From 0f54ad8e26c8ab800948ddf5557f8d96440c434b Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Wed, 15 Nov 2023 16:12:42 +0100 Subject: [PATCH] dialog: add text-area widget This patch moves the text-editing facility of app/text_area to a text-area widget as part of the dialog library. This has two benefits. First, it simplifies app/text_area by using the dialog API. Second, the editor can now easily be reused by other dialog-API-based applications. Fixes #5058 --- .../dialog/text_area_widget.h} | 94 ++--- repos/gems/lib/mk/dialog.mk | 2 +- repos/gems/recipes/src/text_area/content.mk | 7 + repos/gems/recipes/src/text_area/used_apis | 1 + repos/gems/src/app/text_area/child_state.h | 124 ------ repos/gems/src/app/text_area/gui.h | 122 ------ .../src/app/text_area/input_event_handler.h | 28 -- repos/gems/src/app/text_area/main.cc | 331 ++++----------- repos/gems/src/app/text_area/report.h | 89 ---- repos/gems/src/app/text_area/target.mk | 4 +- repos/gems/src/app/text_area/types.h | 21 - .../dialog/text_area_widget.cc} | 394 ++++++++---------- 12 files changed, 310 insertions(+), 907 deletions(-) rename repos/gems/{src/app/text_area/dialog.h => include/dialog/text_area_widget.h} (69%) delete mode 100644 repos/gems/src/app/text_area/child_state.h delete mode 100644 repos/gems/src/app/text_area/gui.h delete mode 100644 repos/gems/src/app/text_area/input_event_handler.h delete mode 100644 repos/gems/src/app/text_area/report.h delete mode 100644 repos/gems/src/app/text_area/types.h rename repos/gems/src/{app/text_area/dialog.cc => lib/dialog/text_area_widget.cc} (60%) diff --git a/repos/gems/src/app/text_area/dialog.h b/repos/gems/include/dialog/text_area_widget.h similarity index 69% rename from repos/gems/src/app/text_area/dialog.h rename to repos/gems/include/dialog/text_area_widget.h index 298b79fead..f98c9e788a 100644 --- a/repos/gems/src/app/text_area/dialog.h +++ b/repos/gems/include/dialog/text_area_widget.h @@ -5,61 +5,40 @@ */ /* - * Copyright (C) 2020 Genode Labs GmbH + * Copyright (C) 2020-2023 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ -#ifndef _DIALOG_H_ -#define _DIALOG_H_ +#ifndef _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_ +#define _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_ /* Genode includes */ -#include -#include -#include -#include -#include -#include +#include #include +#include +#include -/* local includes */ -#include -#include -#include - -namespace Text_area { struct Dialog; } +namespace Dialog { struct Text_area_widget; } -struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer +struct Dialog::Text_area_widget : Widget { public: - Dynamic_rom_session rom_session; - - struct Trigger_copy : Interface, Noncopyable + struct Action : Interface, Noncopyable { virtual void trigger_copy() = 0; - }; - - struct Trigger_paste : Interface, Noncopyable - { virtual void trigger_paste() = 0; - }; - - struct Trigger_save : Interface, Noncopyable - { virtual void trigger_save() = 0; + virtual void refresh_text_area() = 0; }; private: Allocator &_alloc; - Trigger_copy &_trigger_copy; - Trigger_paste &_trigger_paste; - Trigger_save &_trigger_save; - struct Character : Codepoint { Character(Codepoint &codepoint) : Codepoint(codepoint) { } @@ -85,16 +64,6 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer Line::Index x; Text::Index y; - Position(Line::Index x, Text::Index y) : x(x), y(y) { } - - Position(Position const &other) : x(other.x), y(other.y) { } - - Position &operator = (Position const &other) - { - x = other.x, y = other.y; - return *this; - } - bool operator != (Position const &other) const { return (x.value != other.x.value) || (y.value != other.y.value); @@ -136,18 +105,15 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer void with_selection_at_line(Text::Index y, Line const &, FN const &) const; /* generate dialog model */ - void gen_selected_line(Xml_generator &, Text::Index, Line const &) const; + void view_selected_line(Scope &, Text::Index, Line const &) const; }; - bool _drag = false; - bool _shift = false; - bool _control = false; - bool _text_hovered = false; + bool _drag = false; + bool _shift = false; + bool _control = false; Selection _selection { }; - void produce_xml(Xml_generator &xml) override; - static bool _printable(Codepoint code) { if (!code.valid()) @@ -192,6 +158,7 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer - _max_lines; } + void _sanitize_scroll_position(); void _move_characters(Line &, Line &); void _delete_selection(); void _insert_printable(Codepoint); @@ -208,10 +175,17 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer void _handle_home(); void _handle_end(); + void _with_position_at(At const &, auto const &) const; + public: - Dialog(Entrypoint &, Ram_allocator &, Region_map &, Allocator &, - Trigger_copy &, Trigger_paste &, Trigger_save &); + Text_area_widget(Allocator &alloc) : _alloc(alloc) { clear(); } + + void view(Scope &) const; + + void click(Clicked_at const &); + void clack(Clacked_at const &, Action &); + void drag (Dragged_at const &); void editable(bool editable) { _editable = editable; } @@ -219,25 +193,15 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer void max_lines(unsigned max_lines) { _max_lines = max_lines; } - void handle_input_event(Input::Event const &); + void handle_event(Event const &, Action &); - void handle_hover(Xml_node const &hover); + void move_cursor_to(::Dialog::At const &); void clear(); - void append_newline() - { - _text.append(_alloc); - } + void append_newline() { _text.append(_alloc); } - void append_character(Codepoint c) - { - if (_printable(c)) { - Text::Index const y { _text.upper_bound().value - 1 }; - _text.apply(y, [&] (Line &line) { - line.append(c); }); - } - } + void append_character(Codepoint c); /* insert character and advance cursor */ void insert_at_cursor_position(Codepoint); @@ -257,4 +221,4 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer } }; -#endif /* _DIALOG_H_ */ +#endif /* _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_ */ diff --git a/repos/gems/lib/mk/dialog.mk b/repos/gems/lib/mk/dialog.mk index 34d832ca82..7b20a82d7f 100644 --- a/repos/gems/lib/mk/dialog.mk +++ b/repos/gems/lib/mk/dialog.mk @@ -1,4 +1,4 @@ -SRC_CC += sandboxed_runtime.cc +SRC_CC += sandboxed_runtime.cc text_area_widget.cc LIBS += sandbox vpath %.cc $(REP_DIR)/src/lib/dialog diff --git a/repos/gems/recipes/src/text_area/content.mk b/repos/gems/recipes/src/text_area/content.mk index 28780d322b..074eb25eb9 100644 --- a/repos/gems/recipes/src/text_area/content.mk +++ b/repos/gems/recipes/src/text_area/content.mk @@ -1,3 +1,10 @@ SRC_DIR := src/app/text_area +MIRROR_FROM_REP_DIR := lib/mk/dialog.mk src/lib/dialog include/dialog + +content: $(MIRROR_FROM_REP_DIR) + +$(MIRROR_FROM_REP_DIR): + $(mirror_from_rep_dir) + include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/repos/gems/recipes/src/text_area/used_apis b/repos/gems/recipes/src/text_area/used_apis index 817b332130..fe8a8f9881 100644 --- a/repos/gems/recipes/src/text_area/used_apis +++ b/repos/gems/recipes/src/text_area/used_apis @@ -8,3 +8,4 @@ timer_session gui_session input_session framebuffer_session +gems diff --git a/repos/gems/src/app/text_area/child_state.h b/repos/gems/src/app/text_area/child_state.h deleted file mode 100644 index 49e04ea9db..0000000000 --- a/repos/gems/src/app/text_area/child_state.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * \brief Runtime state of a child hosted in the runtime subsystem - * \author Norman Feske - * \date 2018-09-03 - */ - -/* - * Copyright (C) 2018 Genode Labs GmbH - * - * This file is part of the Genode OS framework, which is distributed - * under the terms of the GNU Affero General Public License version 3. - */ - -#ifndef _CHILD_STATE_H_ -#define _CHILD_STATE_H_ - -/* Genode includes */ -#include -#include -#include -#include -#include - -/* local includes */ -#include "types.h" - -namespace Text_area { struct Child_state; } - -struct Text_area::Child_state : Noncopyable -{ - private: - - using Start_name = String<128>; - - Registry::Element _element; - - Start_name const _name; - - Ram_quota const _initial_ram_quota; - Cap_quota const _initial_cap_quota; - - Ram_quota _ram_quota = _initial_ram_quota; - Cap_quota _cap_quota = _initial_cap_quota; - - struct Version { unsigned value; } _version { 0 }; - - public: - - /** - * Constructor - * - * \param ram_quota initial RAM quota - * \param cap_quota initial capability quota - */ - Child_state(Registry ®istry, Start_name const &name, - Ram_quota ram_quota, Cap_quota cap_quota) - : - _element(registry, *this), - _name(name), - _initial_ram_quota(ram_quota), _initial_cap_quota(cap_quota) - { } - - void trigger_restart() - { - _version.value++; - _ram_quota = _initial_ram_quota; - _cap_quota = _initial_cap_quota; - } - - void gen_start_node_version(Xml_generator &xml) const - { - if (_version.value) - xml.attribute("version", _version.value); - } - - void gen_start_node_content(Xml_generator &xml) const - { - xml.attribute("name", _name); - - gen_start_node_version(xml); - - xml.attribute("caps", _cap_quota.value); - xml.node("resource", [&] () { - xml.attribute("name", "RAM"); - Number_of_bytes const bytes(_ram_quota.value); - xml.attribute("quantum", String<64>(bytes)); }); - } - - /** - * Adapt runtime state information to the child - * - * This method responds to RAM and cap-resource requests by increasing - * the resource quotas as needed. - * - * \param child child node of the runtime'r state report - * \return true if runtime must be reconfigured so that the changes - * can take effect - */ - bool apply_child_state_report(Xml_node child) - { - bool result = false; - - if (child.attribute_value("name", Start_name()) != _name) - return false; - - if (child.has_sub_node("ram") && child.sub_node("ram").has_attribute("requested")) { - _ram_quota.value *= 2; - result = true; - } - - if (child.has_sub_node("caps") && child.sub_node("caps").has_attribute("requested")) { - _cap_quota.value += 100; - result = true; - } - - return result; - } - - Ram_quota ram_quota() const { return _ram_quota; } - - Start_name name() const { return _name; } -}; - -#endif /* _CHILD_STATE_H_ */ diff --git a/repos/gems/src/app/text_area/gui.h b/repos/gems/src/app/text_area/gui.h deleted file mode 100644 index 1b47d54dbb..0000000000 --- a/repos/gems/src/app/text_area/gui.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * \brief GUI wrapper for monitoring the user input of GUI components - * \author Norman Feske - * \date 2020-01-12 - */ - -/* - * Copyright (C) 2020 Genode Labs GmbH - * - * This file is part of the Genode OS framework, which is distributed - * under the terms of the GNU Affero General Public License version 3. - */ - -#ifndef _GUI_H_ -#define _GUI_H_ - -/* Genode includes */ -#include -#include -#include - -/* local includes */ -#include - -namespace Gui { - - using namespace Genode; - - struct Session_component; -} - - -struct Gui::Session_component : Session_object -{ - Env &_env; - - Input_event_handler &_event_handler; - - Gui::Connection _connection; - - Input::Session_component _input_component { _env, _env.ram() }; - - Signal_handler _input_handler { - _env.ep(), *this, &Session_component::_handle_input }; - - void _handle_input() - { - _connection.input()->for_each_event([&] (Input::Event ev) { - - /* handle event locally within the sculpt manager */ - _event_handler.handle_input_event(ev); - - _input_component.submit(ev); - }); - } - - template - Session_component(Env &env, Input_event_handler &event_handler, ARGS &&... args) - : - Session_object(args...), - _env(env), _event_handler(event_handler), - _connection(env, _label.string()) - { - _connection.input()->sigh(_input_handler); - _env.ep().manage(_input_component); - _input_component.event_queue().enabled(true); - } - - ~Session_component() { _env.ep().dissolve(_input_component); } - - void upgrade(Session::Resources const &resources) - { - _connection.upgrade(resources); - } - - Framebuffer::Session_capability framebuffer_session() override { - return _connection.framebuffer_session(); } - - Input::Session_capability input_session() override { - return _input_component.cap(); } - - View_handle create_view(View_handle parent) override { - return _connection.create_view(parent); } - - void destroy_view(View_handle view) override { - _connection.destroy_view(view); } - - View_handle view_handle(View_capability view_cap, View_handle handle) override { - return _connection.view_handle(view_cap, handle); } - - View_capability view_capability(View_handle view) override { - return _connection.view_capability(view); } - - void release_view_handle(View_handle view) override { - _connection.release_view_handle(view); } - - Dataspace_capability command_dataspace() override { - return _connection.command_dataspace(); } - - void execute() override { - _connection.execute(); } - - Framebuffer::Mode mode() override { - return _connection.mode(); } - - void mode_sigh(Signal_context_capability sigh) override { - _connection.mode_sigh(sigh); } - - void buffer(Framebuffer::Mode mode, bool use_alpha) override - { - /* - * Do not call 'Connection::buffer' to avoid paying session quota - * from our own budget. - */ - _connection.Client::buffer(mode, use_alpha); - } - - void focus(Capability session) override { - _connection.focus(session); } -}; - -#endif /* _GUI_H_ */ diff --git a/repos/gems/src/app/text_area/input_event_handler.h b/repos/gems/src/app/text_area/input_event_handler.h deleted file mode 100644 index 25329dcb52..0000000000 --- a/repos/gems/src/app/text_area/input_event_handler.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * \brief Interface for handling input events - * \author Norman Feske - * \date 2018-05-02 - */ - -/* - * Copyright (C) 2018 Genode Labs GmbH - * - * This file is part of the Genode OS framework, which is distributed - * under the terms of the GNU Affero General Public License version 3. - */ - -#ifndef _INPUT_EVENT_HANDLER_H_ -#define _INPUT_EVENT_HANDLER_H_ - -/* Genode includes */ -#include -#include - -namespace Gui { struct Input_event_handler; } - -struct Gui::Input_event_handler : Genode::Interface -{ - virtual void handle_input_event(Input::Event const &) = 0; -}; - -#endif /* _INPUT_EVENT_HANDLER_H_ */ diff --git a/repos/gems/src/app/text_area/main.cc b/repos/gems/src/app/text_area/main.cc index b6c2112ab9..1b446b0833 100644 --- a/repos/gems/src/app/text_area/main.cc +++ b/repos/gems/src/app/text_area/main.cc @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2020 Genode Labs GmbH + * Copyright (C) 2020-2023 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -13,28 +13,21 @@ /* Genode includes */ #include -#include #include -#include -#include -#include -#include -#include +#include +#include #include +#include -/* local includes */ -#include -#include -#include -#include +namespace Text_area { -namespace Text_area { struct Main; } + using namespace Dialog; -struct Text_area::Main : Sandbox::Local_service_base::Wakeup, - Sandbox::State_handler, - Gui::Input_event_handler, - Dialog::Trigger_copy, Dialog::Trigger_paste, - Dialog::Trigger_save + struct Main; +} + + +struct Text_area::Main : Text_area_widget::Action { Env &_env; @@ -47,60 +40,69 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup, unsigned _min_width = 0; unsigned _min_height = 0; - Registry _children { }; + Runtime _runtime { _env, _heap }; - Child_state _menu_view_child_state { _children, "menu_view", - Ram_quota { 4*1024*1024 }, - Cap_quota { 200 } }; - /** - * Sandbox::State_handler - */ - void handle_sandbox_state() override + struct Main_dialog : Top_level_dialog { - /* obtain current sandbox state */ - Buffered_xml state(_heap, "state", [&] (Xml_generator &xml) { - _sandbox.generate_state_report(xml); + Main &_main; + + Hosted text { Id { "text" }, _main._heap }; + + Main_dialog(Main &main) : Top_level_dialog("text_area"), _main(main) { } + + void view(Scope<> &s) const override + { + s.sub_scope([&] (Scope &s) { + s.sub_scope