diff --git a/Base/res/apps/ModelGallery.af b/Base/res/apps/ModelGallery.af new file mode 100644 index 0000000000..3944821506 --- /dev/null +++ b/Base/res/apps/ModelGallery.af @@ -0,0 +1,4 @@ +[App] +Name=Model Gallery +Executable=/bin/ModelGallery +Category=Demos diff --git a/Base/res/icons/16x16/app-model-gallery.png b/Base/res/icons/16x16/app-model-gallery.png new file mode 100644 index 0000000000..59d865b131 Binary files /dev/null and b/Base/res/icons/16x16/app-model-gallery.png differ diff --git a/Base/res/icons/32x32/app-model-gallery.png b/Base/res/icons/32x32/app-model-gallery.png new file mode 100644 index 0000000000..5e5c850874 Binary files /dev/null and b/Base/res/icons/32x32/app-model-gallery.png differ diff --git a/Userland/Demos/CMakeLists.txt b/Userland/Demos/CMakeLists.txt index 3fe8445740..f75aa4adc7 100644 --- a/Userland/Demos/CMakeLists.txt +++ b/Userland/Demos/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(Fire) add_subdirectory(LibGfxDemo) add_subdirectory(LibGfxScaleDemo) add_subdirectory(Mandelbrot) +add_subdirectory(ModelGallery) add_subdirectory(Mouse) add_subdirectory(Screensaver) add_subdirectory(Starfield) diff --git a/Userland/Demos/ModelGallery/BasicModel.cpp b/Userland/Demos/ModelGallery/BasicModel.cpp new file mode 100644 index 0000000000..10b76e37af --- /dev/null +++ b/Userland/Demos/ModelGallery/BasicModel.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "BasicModel.h" + +GUI::Variant BasicModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + if (role != GUI::ModelRole::Display) + return {}; + if (!is_within_range(index)) + return {}; + + return m_items.at(index.row()); +} + +TriState BasicModel::data_matches(GUI::ModelIndex const& index, GUI::Variant const& data) const +{ + if (!is_within_range(index)) + return TriState::False; + if (!data.is_string()) + return TriState::False; + + auto& value = m_items.at(index.row()); + return value.contains(data.as_string()) ? TriState::True : TriState::False; +} + +void BasicModel::invalidate() +{ + Model::invalidate(); + if (on_invalidate) + on_invalidate(); +} + +GUI::ModelIndex BasicModel::index(int row, int column, GUI::ModelIndex const& parent) const +{ + if (column != 0) + return {}; + if (parent.is_valid()) + return {}; + if (row < 0 || row >= static_cast(m_items.size())) + return {}; + + return create_index(row, column); +} + +void BasicModel::add_item(String const& item) +{ + begin_insert_rows({}, m_items.size(), m_items.size()); + m_items.append(item); + end_insert_rows(); + + did_update(UpdateFlag::DontInvalidateIndices); +} + +void BasicModel::remove_item(GUI::ModelIndex const& index) +{ + if (!index.is_valid() || !is_within_range(index)) + return; + + begin_delete_rows({}, index.row(), index.row()); + m_items.remove(index.row()); + end_delete_rows(); + + did_update(UpdateFlag::DontInvalidateIndices); +} diff --git a/Userland/Demos/ModelGallery/BasicModel.h b/Userland/Demos/ModelGallery/BasicModel.h new file mode 100644 index 0000000000..e0dfd1ed4c --- /dev/null +++ b/Userland/Demos/ModelGallery/BasicModel.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +class BasicModel final : public GUI::Model { +public: + static NonnullRefPtr create() + { + return adopt_ref(*new BasicModel()); + } + + virtual int row_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override { return m_items.size(); } + virtual int column_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override { return 1; } + virtual String column_name(int) const override { return "Item"; } + + virtual GUI::Variant data(GUI::ModelIndex const&, GUI::ModelRole = GUI::ModelRole::Display) const override; + virtual TriState data_matches(GUI::ModelIndex const&, GUI::Variant const&) const override; + virtual void invalidate() override; + virtual GUI::ModelIndex index(int row, int column = 0, GUI::ModelIndex const& parent = GUI::ModelIndex()) const override; + + Function on_invalidate; + + void add_item(String const& item); + void remove_item(GUI::ModelIndex const&); + +private: + BasicModel() + { + } + + Vector m_items; +}; diff --git a/Userland/Demos/ModelGallery/BasicModelTab.gml b/Userland/Demos/ModelGallery/BasicModelTab.gml new file mode 100644 index 0000000000..f645563a52 --- /dev/null +++ b/Userland/Demos/ModelGallery/BasicModelTab.gml @@ -0,0 +1,44 @@ +@GUI::Widget { + name: "basic_model_tab" + layout: @GUI::VerticalBoxLayout { + margins: [4] + } + + @GUI::Label { + text: "Here is a basic model, displayed on a table widget. Its clients are updated via granular updates. You can add or remove items with the widgets below." + text_alignment: "CenterLeft" + + thickness: 2 + fixed_height: 34 + } + + @GUI::TableView { + name: "model_table" + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + } + + fixed_height: 30 + + @GUI::TextBox { + name: "new_item_name" + placeholder: "Enter some text to be added..." + } + + @GUI::Button { + name: "add_new_item" + fixed_width: 22 + fixed_height: 22 + tooltip: "Add the text as an item to the model" + } + + @GUI::Button { + name: "remove_selected_item" + fixed_width: 22 + fixed_height: 22 + tooltip: "Remove the selected item from the model" + } + } +} diff --git a/Userland/Demos/ModelGallery/CMakeLists.txt b/Userland/Demos/ModelGallery/CMakeLists.txt new file mode 100644 index 0000000000..cf9f7f9c96 --- /dev/null +++ b/Userland/Demos/ModelGallery/CMakeLists.txt @@ -0,0 +1,17 @@ +serenity_component( + ModelGallery + TARGETS ModelGallery +) + +compile_gml(./BasicModelTab.gml BasicModelTabGML.h basic_model_tab_gml) + +set(SOURCES + main.cpp + GalleryWidget.cpp + BasicModel.cpp + BasicModelTabGML.h +) + +serenity_app(ModelGallery ICON app-model-gallery) + +target_link_libraries(ModelGallery LibGUI LibGfx) diff --git a/Userland/Demos/ModelGallery/GalleryWidget.cpp b/Userland/Demos/ModelGallery/GalleryWidget.cpp new file mode 100644 index 0000000000..28c77ce1f4 --- /dev/null +++ b/Userland/Demos/ModelGallery/GalleryWidget.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GalleryWidget.h" +#include + +GalleryWidget::GalleryWidget() +{ + set_fill_with_background_color(true); + set_layout(); + + auto& inner_widget = add(); + auto& inner_layout = inner_widget.set_layout(); + inner_layout.set_margins({ 4 }); + + m_tab_widget = inner_widget.add(); + m_statusbar = add(); + + load_basic_model_tab(); + load_sorting_filtering_tab(); +} + +void GalleryWidget::load_basic_model_tab() +{ + auto& tab = m_tab_widget->add_tab("Basic Model"); + tab.load_from_gml(basic_model_tab_gml); + + m_basic_model = BasicModel::create(); + m_basic_model_table = *tab.find_descendant_of_type_named("model_table"); + m_basic_model_table->set_model(m_basic_model); + + m_basic_model->on_invalidate = [&] { + m_invalidation_count++; + m_statusbar->set_text(String::formatted("Times invalidated: {}", m_invalidation_count)); + }; + + m_statusbar->set_text(String::formatted("Times invalidated: {}", m_invalidation_count)); + + m_basic_model->add_item("Well..."); + m_basic_model->add_item("...hello..."); + m_basic_model->add_item("...friends! :^)"); + + m_new_item_name = *tab.find_descendant_of_type_named("new_item_name"); + m_add_new_item = *tab.find_descendant_of_type_named("add_new_item"); + m_remove_selected_item = *tab.find_descendant_of_type_named("remove_selected_item"); + + m_add_new_item->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/plus.png")); + m_remove_selected_item->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/minus.png")); + + m_new_item_name->on_return_pressed = [&] { add_textbox_contents_to_basic_model(); }; + m_add_new_item->on_click = [&](auto) { add_textbox_contents_to_basic_model(); }; + + m_remove_selected_item->on_click = [&](auto) { + auto index = m_basic_model_table->cursor_index(); + if (index.is_valid()) { + m_basic_model->remove_item(index); + } + }; +} + +void GalleryWidget::load_sorting_filtering_tab() +{ + // TODO: Add the SortingFilteringProxyModel here. +} + +void GalleryWidget::add_textbox_contents_to_basic_model() +{ + if (!m_new_item_name->current_line().is_empty()) { + m_basic_model->add_item(m_new_item_name->current_line().to_utf8()); + m_new_item_name->set_text(""); + } +} diff --git a/Userland/Demos/ModelGallery/GalleryWidget.h b/Userland/Demos/ModelGallery/GalleryWidget.h new file mode 100644 index 0000000000..ddb7a3c0b6 --- /dev/null +++ b/Userland/Demos/ModelGallery/GalleryWidget.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "BasicModel.h" +#include +#include +#include +#include +#include +#include +#include + +class GalleryWidget final : public GUI::Widget { + C_OBJECT(GalleryWidget) + +private: + GalleryWidget(); + + void load_basic_model_tab(); + void load_sorting_filtering_tab(); + + void add_textbox_contents_to_basic_model(); + + RefPtr m_tab_widget; + RefPtr m_statusbar; + + size_t m_invalidation_count { 0 }; + RefPtr m_basic_model; + RefPtr m_basic_model_table; + RefPtr m_new_item_name; + RefPtr m_add_new_item; + RefPtr m_remove_selected_item; +}; diff --git a/Userland/Demos/ModelGallery/main.cpp b/Userland/Demos/ModelGallery/main.cpp new file mode 100644 index 0000000000..acdf0c5876 --- /dev/null +++ b/Userland/Demos/ModelGallery/main.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GalleryWidget.h" +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio recvfd sendfd rpath wpath cpath unix", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto app = GUI::Application::construct(argc, argv); + + if (pledge("stdio recvfd sendfd rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto app_icon = GUI::Icon::default_icon("app-model-gallery"); + + auto window = GUI::Window::construct(); + window->set_title("Model Gallery"); + window->set_icon(app_icon.bitmap_for_size(16)); + window->resize(430, 480); + window->set_main_widget(); + + window->show(); + return app->exec(); +}