/* * Copyright (C) 2023 Kyle Reed * * 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. */ /* TODO: * - Busy indicator while reading files. */ #ifndef __UI_TEXT_EDITOR_H__ #define __UI_TEXT_EDITOR_H__ #include "ui.hpp" #include "ui_navigation.hpp" #include "ui_painter.hpp" #include "ui_styles.hpp" #include "ui_widget.hpp" #include "file_wrapper.hpp" #include "optional.hpp" #include #include namespace ui { enum class ScrollDirection : uint8_t { Vertical, Horizontal }; /* Control that renders a text file. */ class TextViewer : public Widget { public: TextViewer(Rect parent_rect); TextViewer(const TextViewer&) = delete; TextViewer(TextViewer&&) = delete; TextViewer& operator=(const TextViewer&) = delete; TextViewer& operator=(TextViewer&&) = delete; std::function on_select{}; std::function on_cursor_moved{}; void paint(Painter& painter) override; bool on_key(KeyEvent key) override; bool on_encoder(EncoderEvent delta) override; void redraw(bool redraw_text = false); 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; } uint32_t offset() const; void cursor_home(); void cursor_end(); // Gets the length of the current line. uint16_t line_length(); const Style& style() { return *font_style; } void set_font_zoom(bool zoom); void toggle_font_zoom() { set_font_zoom(!font_zoom); }; private: bool font_zoom{}; const Style* font_style{}; int8_t char_width{}; int8_t char_height{}; uint8_t max_line{}; uint8_t max_col{}; /* 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); void reset_file(FileWrapper* file = nullptr); FileWrapper* file_{}; 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}; } paint_state_{}; struct { uint32_t line{}; uint16_t col{}; ScrollDirection dir{ScrollDirection::Vertical}; } cursor_{}; }; /* Menu control for the TextEditor. */ class TextEditorMenu : public View { public: TextEditorMenu(); void on_show() override; void on_hide() override; std::function& on_home() { return button_home.on_select; } std::function& on_end() { return button_end.on_select; } std::function& on_zoom() { return button_zoom.on_select; } std::function& on_delete_line() { return button_delline.on_select; } std::function& on_edit_line() { return button_edit.on_select; } std::function& on_add_line() { return button_addline.on_select; } std::function& on_open() { return button_open.on_select; } std::function& on_save() { return button_save.on_select; } std::function& 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()}; NewButton button_home{ {1 * 8, 1 * 8, 7 * 8, 7 * 8}, "Home", &bitmap_arrow_left, Color::dark_grey()}; NewButton button_end{ {8 * 8, 1 * 8, 7 * 8, 7 * 8}, "End", &bitmap_arrow_right, Color::dark_grey()}; NewButton button_zoom{ {15 * 8, 1 * 8, 7 * 8, 7 * 8}, "Zoom", &bitmap_icon_search, Color::orange()}; 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); ~TextEditorView(); std::string title() const override { return "Notepad"; }; void on_show() override; private: static constexpr size_t max_edit_length = 1024; std::string edit_line_buffer_{}; void open_file(const std::filesystem::path& path); void save_file(); void refresh_ui(); void update_position(); void hide_menu(bool hidden = true); void show_file_picker(bool immediate = true); void show_edit_line(); void show_save_prompt(std::function continuation); void prepare_for_write(); void save_temp_file(); NavigationView& nav_; std::unique_ptr file_{}; std::filesystem::path path_{}; bool file_dirty_{false}; bool has_temp_file_{false}; TextViewer viewer{ /* 272 = 320 - 16 (top bar) - 32 (bottom controls) */ {0, 0, 240, 272}}; TextEditorMenu menu{}; NewButton button_menu{ {26 * 8, 34 * 8, 4 * 8, 4 * 8}, {}, &bitmap_icon_controls, Color::dark_grey()}; Text text_position{ {0 * 8, 34 * 8, 26 * 8, 2 * 8}, ""}; Text text_size{ {0 * 8, 36 * 8, 26 * 8, 2 * 8}, ""}; }; } // namespace ui #endif // __UI_TEXT_EDITOR_H__