From 42df4fb2df21da43412bb747d2cbc1c839275584 Mon Sep 17 00:00:00 2001 From: Maciej Zygmanowski Date: Mon, 2 Aug 2021 12:14:52 +0200 Subject: [PATCH] MouseSettings: Add a tab to change cursor theme --- .../Applications/MouseSettings/CMakeLists.txt | 11 +- .../{MouseSettingsWindow.gml => Mouse.gml} | 0 .../MouseSettings/MouseSettingsWindow.cpp | 72 ++------- .../MouseSettings/MouseSettingsWindow.h | 12 -- .../MouseSettings/MouseWidget.cpp | 74 +++++++++ .../Applications/MouseSettings/MouseWidget.h | 30 ++++ Userland/Applications/MouseSettings/Theme.gml | 39 +++++ .../MouseSettings/ThemeWidget.cpp | 144 ++++++++++++++++++ .../Applications/MouseSettings/ThemeWidget.h | 82 ++++++++++ 9 files changed, 388 insertions(+), 76 deletions(-) rename Userland/Applications/MouseSettings/{MouseSettingsWindow.gml => Mouse.gml} (100%) create mode 100644 Userland/Applications/MouseSettings/MouseWidget.cpp create mode 100644 Userland/Applications/MouseSettings/MouseWidget.h create mode 100644 Userland/Applications/MouseSettings/Theme.gml create mode 100644 Userland/Applications/MouseSettings/ThemeWidget.cpp create mode 100644 Userland/Applications/MouseSettings/ThemeWidget.h diff --git a/Userland/Applications/MouseSettings/CMakeLists.txt b/Userland/Applications/MouseSettings/CMakeLists.txt index dff69c96c1..a266dd7be3 100644 --- a/Userland/Applications/MouseSettings/CMakeLists.txt +++ b/Userland/Applications/MouseSettings/CMakeLists.txt @@ -4,14 +4,19 @@ serenity_component( TARGETS MouseSettings ) -compile_gml(MouseSettingsWindow.gml MouseSettingsWindowGML.h mouse_settings_window_gml) +compile_gml(Mouse.gml MouseWidgetGML.h mouse_widget_gml) +compile_gml(Theme.gml ThemeWidgetGML.h theme_widget_gml) set(SOURCES main.cpp DoubleClickArrowWidget.cpp MouseSettingsWindow.cpp - MouseSettingsWindow.h - MouseSettingsWindowGML.h + MouseWidget.cpp + MouseWidget.h + MouseWidgetGML.h + ThemeWidget.cpp + ThemeWidget.h + ThemeWidgetGML.h ) serenity_app(MouseSettings ICON app-mouse) diff --git a/Userland/Applications/MouseSettings/MouseSettingsWindow.gml b/Userland/Applications/MouseSettings/Mouse.gml similarity index 100% rename from Userland/Applications/MouseSettings/MouseSettingsWindow.gml rename to Userland/Applications/MouseSettings/Mouse.gml diff --git a/Userland/Applications/MouseSettings/MouseSettingsWindow.cpp b/Userland/Applications/MouseSettings/MouseSettingsWindow.cpp index bf170a46d7..c0a7ba7db9 100644 --- a/Userland/Applications/MouseSettings/MouseSettingsWindow.cpp +++ b/Userland/Applications/MouseSettings/MouseSettingsWindow.cpp @@ -7,37 +7,13 @@ */ #include "MouseSettingsWindow.h" -#include "DoubleClickArrowWidget.h" -#include +#include "MouseWidget.h" +#include "ThemeWidget.h" #include #include #include -#include #include #include -#include -#include -#include - -constexpr double speed_slider_scale = 100.0; -constexpr int default_scroll_length = 4; -constexpr int double_click_speed_default = 250; - -void MouseSettingsWindow::update_window_server() -{ - const float factor = m_speed_slider->value() / speed_slider_scale; - GUI::WindowServerConnection::the().async_set_mouse_acceleration(factor); - GUI::WindowServerConnection::the().async_set_scroll_step_size(m_scroll_length_spinbox->value()); - GUI::WindowServerConnection::the().async_set_double_click_speed(m_double_click_speed_slider->value()); -} - -void MouseSettingsWindow::reset_default_values() -{ - m_speed_slider->set_value(speed_slider_scale); - m_scroll_length_spinbox->set_value(default_scroll_length); - m_double_click_speed_slider->set_value(double_click_speed_default); - update_window_server(); -} MouseSettingsWindow::MouseSettingsWindow() { @@ -48,38 +24,9 @@ MouseSettingsWindow::MouseSettingsWindow() main_widget.layout()->set_spacing(6); auto& tab_widget = main_widget.add(); - auto& mouse_widget = tab_widget.add_tab("Mouse"); - mouse_widget.load_from_gml(mouse_settings_window_gml); - m_speed_label = *main_widget.find_descendant_of_type_named("speed_label"); - m_speed_slider = *main_widget.find_descendant_of_type_named("speed_slider"); - m_speed_slider->set_range(WindowServer::mouse_accel_min * speed_slider_scale, WindowServer::mouse_accel_max * speed_slider_scale); - m_speed_slider->on_change = [&](int value) { - m_speed_label->set_text(String::formatted("{} %", value)); - }; - const int slider_value = float { speed_slider_scale } * GUI::WindowServerConnection::the().get_mouse_acceleration(); - m_speed_slider->set_value(slider_value); - - auto& cursor_speed_image_label = *main_widget.find_descendant_of_type_named("cursor_speed_image_label"); - cursor_speed_image_label.set_icon(Gfx::Bitmap::try_load_from_file("/res/graphics/mouse-cursor-speed.png")); - - auto& scroll_step_size_image_label = *main_widget.find_descendant_of_type_named("scroll_step_size_image_label"); - scroll_step_size_image_label.set_icon(Gfx::Bitmap::try_load_from_file("/res/graphics/scroll-wheel-step-size.png")); - - m_scroll_length_spinbox = *main_widget.find_descendant_of_type_named("scroll_length_spinbox"); - m_scroll_length_spinbox->set_min(WindowServer::scroll_step_size_min); - m_scroll_length_spinbox->set_value(GUI::WindowServerConnection::the().get_scroll_step_size()); - - m_double_click_arrow_widget = *main_widget.find_descendant_of_type_named("double_click_arrow_widget"); - m_double_click_speed_label = *main_widget.find_descendant_of_type_named("double_click_speed_label"); - m_double_click_speed_slider = *main_widget.find_descendant_of_type_named("double_click_speed_slider"); - m_double_click_speed_slider->set_min(WindowServer::double_click_speed_min); - m_double_click_speed_slider->set_max(WindowServer::double_click_speed_max); - m_double_click_speed_slider->on_change = [&](int speed) { - m_double_click_arrow_widget->set_double_click_speed(speed); - m_double_click_speed_label->set_text(String::formatted("{} ms", speed)); - }; - m_double_click_speed_slider->set_value(GUI::WindowServerConnection::the().get_double_click_speed()); + auto& mouse_widget = tab_widget.add_tab("Mouse"); + auto& theme_widget = tab_widget.add_tab("Cursor Theme"); auto& button_container = main_widget.add(); button_container.set_shrink_to_fit(true); @@ -87,8 +34,9 @@ MouseSettingsWindow::MouseSettingsWindow() button_container.layout()->set_spacing(6); m_reset_button = button_container.add("Defaults"); - m_reset_button->on_click = [this](auto) { - reset_default_values(); + m_reset_button->on_click = [&](auto) { + mouse_widget.reset_default_values(); + theme_widget.reset_default_values(); }; button_container.layout()->add_spacer(); @@ -96,7 +44,8 @@ MouseSettingsWindow::MouseSettingsWindow() m_ok_button = button_container.add("OK"); m_ok_button->set_fixed_width(75); m_ok_button->on_click = [&](auto) { - update_window_server(); + mouse_widget.update_window_server(); + theme_widget.update_window_server(); GUI::Application::the()->quit(); }; @@ -109,7 +58,8 @@ MouseSettingsWindow::MouseSettingsWindow() m_apply_button = button_container.add("Apply"); m_apply_button->set_fixed_width(75); m_apply_button->on_click = [&](auto) { - update_window_server(); + mouse_widget.update_window_server(); + theme_widget.update_window_server(); }; } diff --git a/Userland/Applications/MouseSettings/MouseSettingsWindow.h b/Userland/Applications/MouseSettings/MouseSettingsWindow.h index 2159636034..3c45d7a96d 100644 --- a/Userland/Applications/MouseSettings/MouseSettingsWindow.h +++ b/Userland/Applications/MouseSettings/MouseSettingsWindow.h @@ -7,10 +7,7 @@ #pragma once -#include "DoubleClickArrowWidget.h" #include -#include -#include #include class MouseSettingsWindow final : public GUI::Window { @@ -21,17 +18,8 @@ public: private: MouseSettingsWindow(); - void update_window_server(); - void reset_default_values(); - - RefPtr m_speed_slider; - RefPtr m_speed_label; - RefPtr m_scroll_length_spinbox; - RefPtr m_double_click_speed_slider; - RefPtr m_double_click_speed_label; RefPtr m_ok_button; RefPtr m_cancel_button; RefPtr m_apply_button; RefPtr m_reset_button; - RefPtr m_double_click_arrow_widget; }; diff --git a/Userland/Applications/MouseSettings/MouseWidget.cpp b/Userland/Applications/MouseSettings/MouseWidget.cpp new file mode 100644 index 0000000000..4dbea84b5f --- /dev/null +++ b/Userland/Applications/MouseSettings/MouseWidget.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "MouseWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +constexpr double speed_slider_scale = 100.0; +constexpr int default_scroll_length = 4; +constexpr int double_click_speed_default = 250; + +MouseWidget::MouseWidget() +{ + load_from_gml(mouse_widget_gml); + + m_speed_label = *find_descendant_of_type_named("speed_label"); + m_speed_slider = *find_descendant_of_type_named("speed_slider"); + m_speed_slider->set_range(WindowServer::mouse_accel_min * speed_slider_scale, WindowServer::mouse_accel_max * speed_slider_scale); + m_speed_slider->on_change = [&](int value) { + m_speed_label->set_text(String::formatted("{} %", value)); + }; + int const slider_value = float { speed_slider_scale } * GUI::WindowServerConnection::the().get_mouse_acceleration(); + m_speed_slider->set_value(slider_value); + + auto& cursor_speed_image_label = *find_descendant_of_type_named("cursor_speed_image_label"); + cursor_speed_image_label.set_icon(Gfx::Bitmap::try_load_from_file("/res/graphics/mouse-cursor-speed.png")); + + auto& scroll_step_size_image_label = *find_descendant_of_type_named("scroll_step_size_image_label"); + scroll_step_size_image_label.set_icon(Gfx::Bitmap::try_load_from_file("/res/graphics/scroll-wheel-step-size.png")); + + m_scroll_length_spinbox = *find_descendant_of_type_named("scroll_length_spinbox"); + m_scroll_length_spinbox->set_min(WindowServer::scroll_step_size_min); + m_scroll_length_spinbox->set_value(GUI::WindowServerConnection::the().get_scroll_step_size()); + + m_double_click_arrow_widget = *find_descendant_of_type_named("double_click_arrow_widget"); + m_double_click_speed_label = *find_descendant_of_type_named("double_click_speed_label"); + m_double_click_speed_slider = *find_descendant_of_type_named("double_click_speed_slider"); + m_double_click_speed_slider->set_min(WindowServer::double_click_speed_min); + m_double_click_speed_slider->set_max(WindowServer::double_click_speed_max); + m_double_click_speed_slider->on_change = [&](int speed) { + m_double_click_arrow_widget->set_double_click_speed(speed); + m_double_click_speed_label->set_text(String::formatted("{} ms", speed)); + }; + m_double_click_speed_slider->set_value(GUI::WindowServerConnection::the().get_double_click_speed()); +} + +void MouseWidget::update_window_server() +{ + float const factor = m_speed_slider->value() / speed_slider_scale; + GUI::WindowServerConnection::the().async_set_mouse_acceleration(factor); + GUI::WindowServerConnection::the().async_set_scroll_step_size(m_scroll_length_spinbox->value()); + GUI::WindowServerConnection::the().async_set_double_click_speed(m_double_click_speed_slider->value()); +} + +void MouseWidget::reset_default_values() +{ + m_speed_slider->set_value(speed_slider_scale); + m_scroll_length_spinbox->set_value(default_scroll_length); + m_double_click_speed_slider->set_value(double_click_speed_default); + update_window_server(); +} + +MouseWidget::~MouseWidget() +{ +} diff --git a/Userland/Applications/MouseSettings/MouseWidget.h b/Userland/Applications/MouseSettings/MouseWidget.h new file mode 100644 index 0000000000..846a70c8f6 --- /dev/null +++ b/Userland/Applications/MouseSettings/MouseWidget.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#include "DoubleClickArrowWidget.h" + +class MouseWidget final : public GUI::Widget { + C_OBJECT(MouseWidget) +public: + virtual ~MouseWidget() override; + + void update_window_server(); + void reset_default_values(); + +private: + MouseWidget(); + + RefPtr m_speed_slider; + RefPtr m_speed_label; + RefPtr m_scroll_length_spinbox; + RefPtr m_double_click_speed_slider; + RefPtr m_double_click_speed_label; + RefPtr m_double_click_arrow_widget; +}; diff --git a/Userland/Applications/MouseSettings/Theme.gml b/Userland/Applications/MouseSettings/Theme.gml new file mode 100644 index 0000000000..1d769d9ffb --- /dev/null +++ b/Userland/Applications/MouseSettings/Theme.gml @@ -0,0 +1,39 @@ +@GUI::Frame { + fill_with_background_color: true + + layout: @GUI::VerticalBoxLayout { + margins: [10, 10, 10, 10] + } + + @GUI::GroupBox { + title: "Theme" + + layout: @GUI::VerticalBoxLayout { + margins: [16, 2, 16, 16] + } + + @GUI::Widget { + fixed_height: 50 + + layout: @GUI::HorizontalBoxLayout { + spacing: 10 + margins: [8, 16, 0, 8] + } + + @GUI::Label { + text: "Select Theme: " + text_alignment: "CenterRight" + } + + @GUI::ComboBox { + name: "theme_name_box" + model_only: true + } + } + + @GUI::TableView { + name: "cursors_tableview" + font_size: 12 + } + } +} diff --git a/Userland/Applications/MouseSettings/ThemeWidget.cpp b/Userland/Applications/MouseSettings/ThemeWidget.cpp new file mode 100644 index 0000000000..aa6619bf64 --- /dev/null +++ b/Userland/Applications/MouseSettings/ThemeWidget.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "ThemeWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +String MouseCursorModel::column_name(int column_index) const +{ + switch (column_index) { + case Column::Bitmap: + return {}; + case Column::Name: + return "Name"; + } + VERIFY_NOT_REACHED(); +} + +GUI::Variant MouseCursorModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + auto& cursor = m_cursors[index.row()]; + + if (role == GUI::ModelRole::Display) { + switch (index.column()) { + case Column::Bitmap: + if (!cursor.bitmap) + return {}; + return *cursor.bitmap; + case Column::Name: + return cursor.name; + } + VERIFY_NOT_REACHED(); + } + return {}; +} + +void MouseCursorModel::invalidate() +{ + if (m_theme_name.is_empty()) + return; + + m_cursors.clear(); + Core::DirIterator iterator(String::formatted("/res/cursor-themes/{}", m_theme_name), Core::DirIterator::Flags::SkipDots); + + while (iterator.has_next()) { + auto path = iterator.next_full_path(); + if (path.ends_with(".ini")) + continue; + if (path.contains("2x")) + continue; + + Cursor cursor; + cursor.path = move(path); + auto filename_split = cursor.path.split('/'); + cursor.name = filename_split[3]; + + // FIXME: Animated cursor bitmaps + auto cursor_bitmap = Gfx::Bitmap::try_load_from_file(cursor.path); + auto cursor_bitmap_rect = cursor_bitmap->rect(); + cursor.params = Gfx::CursorParams::parse_from_filename(cursor.name, cursor_bitmap_rect.center()).constrained(*cursor_bitmap); + cursor.bitmap = cursor_bitmap->cropped(Gfx::IntRect(Gfx::FloatRect(cursor_bitmap_rect).scaled(1.0 / cursor.params.frames(), 1.0))); + + m_cursors.append(move(cursor)); + } + Model::invalidate(); +} + +GUI::Variant ThemeModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + if (role == GUI::ModelRole::Display) { + return m_themes[index.row()]; + } + return {}; +} + +void ThemeModel::invalidate() +{ + m_themes.clear(); + + Core::DirIterator iterator("/res/cursor-themes", Core::DirIterator::Flags::SkipDots); + + while (iterator.has_next()) { + auto path = iterator.next_path(); + if (access(String::formatted("/res/cursor-themes/{}/Config.ini", path).characters(), R_OK) == 0) + m_themes.append(path); + } + Model::invalidate(); +} + +ThemeWidget::ThemeWidget() +{ + load_from_gml(theme_widget_gml); + m_cursors_tableview = find_descendant_of_type_named("cursors_tableview"); + m_cursors_tableview->set_highlight_selected_rows(true); + m_cursors_tableview->set_alternating_row_colors(false); + m_cursors_tableview->set_vertical_padding(16); + m_cursors_tableview->set_column_headers_visible(false); + m_cursors_tableview->set_highlight_key_column(false); + + auto mouse_cursor_model = MouseCursorModel::create(); + auto sorting_proxy_model = GUI::SortingProxyModel::create(mouse_cursor_model); + sorting_proxy_model->set_sort_role(GUI::ModelRole::Display); + + m_cursors_tableview->set_model(sorting_proxy_model); + m_cursors_tableview->set_key_column_and_sort_order(MouseCursorModel::Column::Name, GUI::SortOrder::Ascending); + m_cursors_tableview->set_column_width(0, 25); + m_cursors_tableview->model()->invalidate(); + + m_theme_name = GUI::WindowServerConnection::the().get_cursor_theme(); + mouse_cursor_model->change_theme(m_theme_name); + + m_theme_name_box = find_descendant_of_type_named("theme_name_box"); + m_theme_name_box->on_change = [this, mouse_cursor_model](String const& value, GUI::ModelIndex const&) mutable { + m_theme_name = value; + mouse_cursor_model->change_theme(m_theme_name); + }; + m_theme_name_box->set_model(ThemeModel::create()); + m_theme_name_box->model()->invalidate(); + m_theme_name_box->set_text(m_theme_name); +} + +void ThemeWidget::update_window_server() +{ + GUI::WindowServerConnection::the().async_apply_cursor_theme(m_theme_name_box->text()); +} + +void ThemeWidget::reset_default_values() +{ + m_theme_name_box->set_text("Default"); + update_window_server(); +} + +ThemeWidget::~ThemeWidget() +{ +} diff --git a/Userland/Applications/MouseSettings/ThemeWidget.h b/Userland/Applications/MouseSettings/ThemeWidget.h new file mode 100644 index 0000000000..58c64af07c --- /dev/null +++ b/Userland/Applications/MouseSettings/ThemeWidget.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +#include "DoubleClickArrowWidget.h" +#include "LibGUI/FilePicker.h" + +class MouseCursorModel final : public GUI::Model { +public: + static NonnullRefPtr create() { return adopt_ref(*new MouseCursorModel); } + virtual ~MouseCursorModel() override { } + + enum Column { + Bitmap, + Name, + __Count, + }; + + virtual int row_count(const GUI::ModelIndex&) const override { return m_cursors.size(); } + virtual int column_count(const GUI::ModelIndex&) const override { return Column::__Count; } + + virtual String column_name(int column_index) const override; + virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override; + virtual void invalidate() override; + + void change_theme(String const& name) + { + m_theme_name = name; + invalidate(); + } + +private: + MouseCursorModel() { } + + struct Cursor { + RefPtr bitmap; + String path; + String name; + Gfx::CursorParams params; + }; + + Vector m_cursors; + String m_theme_name; +}; + +class ThemeModel final : public GUI::Model { +public: + static NonnullRefPtr create() { return adopt_ref(*new ThemeModel); } + virtual int row_count(const GUI::ModelIndex&) const override { return m_themes.size(); } + virtual int column_count(const GUI::ModelIndex&) const override { return 1; } + + virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override; + virtual void invalidate() override; + +private: + Vector m_themes; +}; + +class ThemeWidget final : public GUI::Widget { + C_OBJECT(ThemeWidget) +public: + virtual ~ThemeWidget() override; + + void update_window_server(); + void reset_default_values(); + +private: + ThemeWidget(); + + RefPtr m_cursors_tableview; + RefPtr m_theme_name_box; + String m_theme_name; +};