2023-05-19 16:39:35 -04:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2023 Kyle Reed
|
2024-02-10 10:56:50 -05:00
|
|
|
* Copyright (C) 2023 Mark Thompson
|
2023-05-19 16:39:35 -04:00
|
|
|
*
|
|
|
|
* This file is part of PortaPack.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; see the file COPYING. If not, write to
|
|
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef __UI_TEXT_EDITOR_H__
|
|
|
|
#define __UI_TEXT_EDITOR_H__
|
|
|
|
|
|
|
|
#include "ui.hpp"
|
|
|
|
#include "ui_navigation.hpp"
|
|
|
|
#include "ui_painter.hpp"
|
2023-06-07 11:33:32 -04:00
|
|
|
#include "ui_styles.hpp"
|
2023-05-19 16:39:35 -04:00
|
|
|
#include "ui_widget.hpp"
|
|
|
|
|
2023-08-18 15:35:41 -04:00
|
|
|
#include "app_settings.hpp"
|
2023-05-28 11:44:21 -04:00
|
|
|
#include "file_wrapper.hpp"
|
2023-05-22 16:08:59 -04:00
|
|
|
#include "optional.hpp"
|
2023-05-19 16:39:35 -04:00
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
#include <memory>
|
2023-05-19 16:39:35 -04:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
namespace ui {
|
|
|
|
|
|
|
|
enum class ScrollDirection : uint8_t {
|
|
|
|
Vertical,
|
|
|
|
Horizontal
|
|
|
|
};
|
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
/* Control that renders a text file. */
|
|
|
|
class TextViewer : public Widget {
|
2023-05-19 16:39:35 -04:00
|
|
|
public:
|
2023-05-26 04:02:17 -04:00
|
|
|
TextViewer(Rect parent_rect);
|
2023-05-19 16:39:35 -04:00
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
TextViewer(const TextViewer&) = delete;
|
|
|
|
TextViewer(TextViewer&&) = delete;
|
|
|
|
TextViewer& operator=(const TextViewer&) = delete;
|
|
|
|
TextViewer& operator=(TextViewer&&) = delete;
|
|
|
|
|
|
|
|
std::function<void()> on_select{};
|
|
|
|
std::function<void()> on_cursor_moved{};
|
2023-05-19 16:39:35 -04:00
|
|
|
|
|
|
|
void paint(Painter& painter) override;
|
2023-06-03 22:26:39 -04:00
|
|
|
bool on_key(KeyEvent key) override;
|
2023-05-19 16:39:35 -04:00
|
|
|
bool on_encoder(EncoderEvent delta) override;
|
|
|
|
|
2023-11-30 15:57:22 -05:00
|
|
|
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
2023-05-26 04:02:17 -04:00
|
|
|
|
|
|
|
void set_file(FileWrapper& file) { reset_file(&file); }
|
|
|
|
void clear_file() { reset_file(); }
|
|
|
|
bool has_file() const { return file_ != nullptr; }
|
|
|
|
|
|
|
|
uint32_t line() const { return cursor_.line; }
|
|
|
|
uint32_t col() const { return cursor_.col; }
|
2023-06-01 18:45:55 -04:00
|
|
|
uint32_t offset() const;
|
|
|
|
|
2023-06-29 16:17:31 -04:00
|
|
|
void cursor_home();
|
|
|
|
void cursor_end();
|
2023-11-30 15:57:22 -05:00
|
|
|
void cursor_set(uint16_t line, uint16_t col);
|
|
|
|
void cursor_mark_selected();
|
2023-12-02 02:05:29 -05:00
|
|
|
void cursor_clear_marked();
|
2023-11-30 15:57:22 -05:00
|
|
|
|
|
|
|
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
2023-12-02 02:05:29 -05:00
|
|
|
std::vector<LineColPair> lineColPair{};
|
2023-06-29 16:17:31 -04:00
|
|
|
|
2023-06-01 18:45:55 -04:00
|
|
|
// Gets the length of the current line.
|
|
|
|
uint16_t line_length();
|
2023-05-26 04:02:17 -04:00
|
|
|
|
2023-06-29 13:55:25 -04:00
|
|
|
const Style& style() { return *font_style; }
|
2023-06-29 16:07:39 -04:00
|
|
|
void set_font_zoom(bool zoom);
|
2023-08-18 15:35:41 -04:00
|
|
|
bool toggle_font_zoom() {
|
|
|
|
set_font_zoom(!font_zoom);
|
|
|
|
return font_zoom;
|
|
|
|
};
|
2023-06-29 13:55:25 -04:00
|
|
|
|
|
|
|
private:
|
|
|
|
bool font_zoom{};
|
|
|
|
const Style* font_style{};
|
|
|
|
int8_t char_width{};
|
|
|
|
int8_t char_height{};
|
|
|
|
uint8_t max_line{};
|
|
|
|
uint8_t max_col{};
|
2023-05-26 04:02:17 -04:00
|
|
|
|
2023-05-19 16:39:35 -04:00
|
|
|
/* Returns true if the cursor was updated. */
|
|
|
|
bool apply_scrolling_constraints(
|
|
|
|
int16_t delta_line,
|
|
|
|
int16_t delta_col);
|
|
|
|
|
|
|
|
void paint_text(Painter& painter, uint32_t line, uint16_t col);
|
|
|
|
void paint_cursor(Painter& painter);
|
2023-11-30 15:57:22 -05:00
|
|
|
void paint_marked(Painter& painter);
|
2023-05-19 16:39:35 -04:00
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
void reset_file(FileWrapper* file = nullptr);
|
|
|
|
|
|
|
|
FileWrapper* file_{};
|
2023-05-19 16:39:35 -04:00
|
|
|
|
|
|
|
struct {
|
|
|
|
// Previous cursor state.
|
|
|
|
uint32_t line{};
|
|
|
|
uint16_t col{};
|
|
|
|
|
|
|
|
// Previous draw state.
|
|
|
|
uint32_t first_line{};
|
|
|
|
uint16_t first_col{};
|
|
|
|
bool redraw_text{true};
|
2023-11-30 15:57:22 -05:00
|
|
|
bool redraw_marked{false};
|
|
|
|
bool mark_change{true};
|
2023-05-19 16:39:35 -04:00
|
|
|
} paint_state_{};
|
|
|
|
|
|
|
|
struct {
|
|
|
|
uint32_t line{};
|
|
|
|
uint16_t col{};
|
|
|
|
ScrollDirection dir{ScrollDirection::Vertical};
|
2023-07-27 10:14:02 -04:00
|
|
|
|
|
|
|
// Pixel buffer used for cursor XOR'ing - Max cursor width = Max char width + 1
|
|
|
|
ColorRGB888 pixel_buffer8[ui::char_width + 1]{};
|
|
|
|
Color pixel_buffer[ui::char_width + 1]{};
|
2023-11-30 15:57:22 -05:00
|
|
|
|
|
|
|
bool mark_change{true};
|
2023-05-19 16:39:35 -04:00
|
|
|
} cursor_{};
|
2023-05-26 04:02:17 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Menu control for the TextEditor. */
|
|
|
|
class TextEditorMenu : public View {
|
|
|
|
public:
|
|
|
|
TextEditorMenu();
|
|
|
|
|
|
|
|
void on_show() override;
|
|
|
|
void on_hide() override;
|
|
|
|
|
2023-06-29 16:17:31 -04:00
|
|
|
std::function<void()>& on_home() { return button_home.on_select; }
|
|
|
|
std::function<void()>& on_end() { return button_end.on_select; }
|
2023-06-29 16:58:10 -04:00
|
|
|
std::function<void()>& on_zoom() { return button_zoom.on_select; }
|
2023-05-26 04:02:17 -04:00
|
|
|
std::function<void()>& on_delete_line() { return button_delline.on_select; }
|
|
|
|
std::function<void()>& on_edit_line() { return button_edit.on_select; }
|
|
|
|
std::function<void()>& on_add_line() { return button_addline.on_select; }
|
|
|
|
std::function<void()>& on_open() { return button_open.on_select; }
|
|
|
|
std::function<void()>& on_save() { return button_save.on_select; }
|
|
|
|
std::function<void()>& on_exit() { return button_exit.on_select; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
void hide_children(bool hidden);
|
|
|
|
|
|
|
|
Rectangle rect_frame{
|
|
|
|
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
|
|
|
|
Color::dark_grey()};
|
|
|
|
|
2023-06-29 16:17:31 -04:00
|
|
|
NewButton button_home{
|
2023-05-26 04:02:17 -04:00
|
|
|
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
|
2023-06-29 16:17:31 -04:00
|
|
|
"Home",
|
|
|
|
&bitmap_arrow_left,
|
2023-05-26 04:02:17 -04:00
|
|
|
Color::dark_grey()};
|
|
|
|
|
2023-06-29 16:17:31 -04:00
|
|
|
NewButton button_end{
|
2023-05-26 04:02:17 -04:00
|
|
|
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
|
2023-06-29 16:17:31 -04:00
|
|
|
"End",
|
|
|
|
&bitmap_arrow_right,
|
2023-05-26 04:02:17 -04:00
|
|
|
Color::dark_grey()};
|
|
|
|
|
2023-06-29 13:55:25 -04:00
|
|
|
NewButton button_zoom{
|
2023-05-26 04:02:17 -04:00
|
|
|
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
|
2023-06-29 13:55:25 -04:00
|
|
|
"Zoom",
|
|
|
|
&bitmap_icon_search,
|
2023-06-30 01:33:33 -04:00
|
|
|
Color::orange()};
|
2023-05-26 04:02:17 -04:00
|
|
|
|
|
|
|
NewButton button_delline{
|
|
|
|
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
|
|
|
|
"-Line",
|
|
|
|
&bitmap_icon_delete,
|
|
|
|
Color::dark_red()};
|
|
|
|
|
|
|
|
NewButton button_edit{
|
|
|
|
{8 * 8, 8 * 8, 7 * 8, 7 * 8},
|
|
|
|
"Edit",
|
|
|
|
&bitmap_icon_rename,
|
|
|
|
Color::dark_blue()};
|
|
|
|
|
|
|
|
NewButton button_addline{
|
|
|
|
{15 * 8, 8 * 8, 7 * 8, 7 * 8},
|
|
|
|
"+Line",
|
|
|
|
&bitmap_icon_scanner,
|
|
|
|
Color::dark_blue()};
|
|
|
|
|
|
|
|
NewButton button_open{
|
|
|
|
{1 * 8, 15 * 8, 7 * 8, 7 * 8},
|
|
|
|
"Open",
|
|
|
|
&bitmap_icon_load,
|
|
|
|
Color::green()};
|
|
|
|
|
|
|
|
NewButton button_save{
|
|
|
|
{8 * 8, 15 * 8, 7 * 8, 7 * 8},
|
|
|
|
"Save",
|
|
|
|
&bitmap_icon_save,
|
|
|
|
Color::green()};
|
|
|
|
|
|
|
|
NewButton button_exit{
|
|
|
|
{15 * 8, 15 * 8, 7 * 8, 7 * 8},
|
|
|
|
"Exit",
|
|
|
|
&bitmap_icon_previous,
|
|
|
|
Color::dark_red()};
|
|
|
|
};
|
|
|
|
|
|
|
|
/* View viewing and minor edits on a text file. */
|
|
|
|
class TextEditorView : public View {
|
|
|
|
public:
|
|
|
|
TextEditorView(NavigationView& nav);
|
|
|
|
TextEditorView(
|
|
|
|
NavigationView& nav,
|
|
|
|
const std::filesystem::path& path);
|
2023-06-01 18:45:55 -04:00
|
|
|
~TextEditorView();
|
2023-05-26 04:02:17 -04:00
|
|
|
|
|
|
|
std::string title() const override {
|
|
|
|
return "Notepad";
|
|
|
|
};
|
|
|
|
|
|
|
|
void on_show() override;
|
|
|
|
|
|
|
|
private:
|
2023-08-18 15:35:41 -04:00
|
|
|
// Settings
|
|
|
|
bool enable_zoom = false;
|
|
|
|
SettingsStore settings_store_{
|
2023-08-19 12:02:26 -04:00
|
|
|
"notepad"sv,
|
|
|
|
{{"enable_zoom"sv, &enable_zoom}}};
|
2023-08-18 15:35:41 -04:00
|
|
|
|
2023-06-01 18:45:55 -04:00
|
|
|
static constexpr size_t max_edit_length = 1024;
|
|
|
|
std::string edit_line_buffer_{};
|
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
void open_file(const std::filesystem::path& path);
|
2023-06-01 18:45:55 -04:00
|
|
|
void save_file();
|
2023-05-26 04:02:17 -04:00
|
|
|
void refresh_ui();
|
|
|
|
void update_position();
|
|
|
|
void hide_menu(bool hidden = true);
|
2023-09-27 15:03:02 -04:00
|
|
|
void show_file_picker();
|
2023-06-01 18:45:55 -04:00
|
|
|
void show_edit_line();
|
|
|
|
void show_save_prompt(std::function<void()> continuation);
|
|
|
|
|
|
|
|
void prepare_for_write();
|
|
|
|
void save_temp_file();
|
2023-05-26 04:02:17 -04:00
|
|
|
|
|
|
|
NavigationView& nav_;
|
|
|
|
std::unique_ptr<FileWrapper> file_{};
|
2023-06-01 18:45:55 -04:00
|
|
|
std::filesystem::path path_{};
|
|
|
|
bool file_dirty_{false};
|
|
|
|
bool has_temp_file_{false};
|
2023-05-26 04:02:17 -04:00
|
|
|
|
|
|
|
TextViewer viewer{
|
2023-09-27 15:03:02 -04:00
|
|
|
/* 272 = screen_height - 16 (top bar) - 32 (bottom controls) */
|
|
|
|
{0, 0, screen_width, 272}};
|
2023-05-19 16:39:35 -04:00
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
TextEditorMenu menu{};
|
2023-05-19 16:39:35 -04:00
|
|
|
|
2023-05-26 04:02:17 -04:00
|
|
|
NewButton button_menu{
|
|
|
|
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
|
|
|
|
{},
|
|
|
|
&bitmap_icon_controls,
|
2023-09-18 17:22:46 -04:00
|
|
|
Color::dark_grey(),
|
|
|
|
/*vcenter*/ true};
|
2023-05-19 16:39:35 -04:00
|
|
|
|
|
|
|
Text text_position{
|
2023-05-26 04:02:17 -04:00
|
|
|
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
|
2023-05-22 16:08:59 -04:00
|
|
|
""};
|
|
|
|
|
|
|
|
Text text_size{
|
2023-05-26 04:02:17 -04:00
|
|
|
{0 * 8, 36 * 8, 26 * 8, 2 * 8},
|
2023-05-19 16:39:35 -04:00
|
|
|
""};
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace ui
|
|
|
|
|
2023-06-29 13:55:25 -04:00
|
|
|
#endif // __UI_TEXT_EDITOR_H__
|