diff --git a/src/panel/widgets/menu.cpp b/src/panel/widgets/menu.cpp index cf1fe75c..a6920af8 100644 --- a/src/panel/widgets/menu.cpp +++ b/src/panel/widgets/menu.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "menu.hpp" #include "gtk-utils.hpp" @@ -15,6 +14,134 @@ const std::string default_icon = "wayfire"; +WfMenuLayout::WfMenuLayout(WayfireMenu *menu) : menu(menu) +{} + +void WfMenuLayout::allocate_vfunc(const Gtk::Widget& widget, int width, int height, int baseline) +{ + if (menu == nullptr) + { + return; + } + + bool is_top = panel_position.value() == WF_WINDOW_POSITION_TOP; + + Gtk::Widget::Measurements entry_measurements, logout_measurements, separator_measurements; + entry_measurements = menu->search_entry.measure(Gtk::Orientation::VERTICAL, width); + logout_measurements = menu->box_bottom.measure(Gtk::Orientation::VERTICAL, width); + separator_measurements = menu->separator.measure(Gtk::Orientation::VERTICAL, width); + + int remaining_height = height - + (entry_measurements.sizes.minimum + + logout_measurements.sizes.minimum + + separator_measurements.sizes.natural); + if (remaining_height <= 0) + { + return; + } + + search_alloc.set_x(0); + search_alloc.set_y(is_top ? 0 : height - + (logout_measurements.sizes.minimum + separator_measurements.sizes.natural + + entry_measurements.sizes.minimum)); + search_alloc.set_height(entry_measurements.sizes.minimum); + search_alloc.set_width(width); + menu->search_entry.size_allocate(search_alloc, -1); + + logout_alloc.set_x(0); + logout_alloc.set_y(height - logout_measurements.sizes.minimum); + logout_alloc.set_width(width); + logout_alloc.set_height(logout_measurements.sizes.minimum); + menu->box_bottom.size_allocate(logout_alloc, -1); + + separator_alloc.set_x(0); + separator_alloc.set_y(height - + (logout_measurements.sizes.minimum + separator_measurements.sizes.natural)); + separator_alloc.set_width(width); + separator_alloc.set_height(separator_measurements.sizes.natural); + menu->separator.size_allocate(separator_alloc, -1); + + if (show_categories.value()) + { + category_alloc.set_x(0); + category_alloc.set_y(is_top ? entry_measurements.sizes.minimum : 0); + category_alloc.set_width(category_width); + category_alloc.set_height(remaining_height); + menu->category_scrolled_window.size_allocate(category_alloc, -1); + + flow_alloc.set_x(category_width); + flow_alloc.set_y(is_top ? entry_measurements.sizes.minimum : 0); + flow_alloc.set_width(width - category_width); + flow_alloc.set_height(remaining_height); + menu->app_scrolled_window.size_allocate(flow_alloc, -1); + } else + { + /* Even if we're not having it, allocate some space */ + category_alloc.set_x(0); + category_alloc.set_y(is_top ? entry_measurements.sizes.minimum : 0); + category_alloc.set_width(width); + category_alloc.set_height(remaining_height); + menu->category_scrolled_window.size_allocate(category_alloc, -1); + + flow_alloc.set_x(0); + flow_alloc.set_y(is_top ? entry_measurements.sizes.minimum : 0); + flow_alloc.set_width(width); + flow_alloc.set_height(remaining_height); + menu->app_scrolled_window.size_allocate(flow_alloc, -1); + } +} + +void WfMenuLayout::measure_vfunc(const Gtk::Widget& widget, Gtk::Orientation orientation, + int for_size, int& minimum, int& natural, int& minimum_baseline, + int& natural_baseline) const +{ + minimum_baseline = -1; + natural_baseline = -1; + // What is our preferred width? + if (orientation == Gtk::Orientation::HORIZONTAL) + { + if (limit_width > 0) + { + minimum = limit_width; + natural = limit_width; + return; + } + + minimum = category_width + content_width; + natural = category_width + content_width; + } else + { + if (limit_height > 0) + { + minimum = limit_height; + natural = limit_height; + return; + } + + Gtk::Widget::Measurements entry_measurements, logout_measurements, separator_measurements; + entry_measurements = menu->search_entry.measure(Gtk::Orientation::VERTICAL, for_size); + logout_measurements = menu->box_bottom.measure(Gtk::Orientation::VERTICAL, for_size); + separator_measurements = menu->separator.measure(Gtk::Orientation::VERTICAL, for_size); + + minimum = separator_measurements.sizes.natural + entry_measurements.sizes.minimum + + logout_measurements.sizes.minimum + content_height; + natural = separator_measurements.sizes.natural + entry_measurements.sizes.minimum + + logout_measurements.sizes.minimum + content_height; + } +} + +void WfMenuLayout::set_limit(int w, int h) +{ + if ((w == limit_width) && (h == limit_height)) + { + return; + } + + limit_width = w; + limit_height = h; + menu->popover_layout_box.queue_resize(); +} + WfMenuCategory::WfMenuCategory(std::string _name, std::string _icon_name) : name(_name), icon_name(_icon_name) {} @@ -488,7 +615,6 @@ void WayfireMenu::on_search_changed() /* Text has been unset, show categories again */ populate_menu_items(category); category_scrolled_window.show(); - app_scrolled_window.set_min_content_width(int(menu_min_content_width)); } else { /* User is filtering, hide categories, ignore chosen category */ @@ -576,6 +702,12 @@ void WayfireMenu::setup_popover_layout() { button->set_popup_child(popover_layout_box); + popover_layout_box.append(app_scrolled_window); + popover_layout_box.append(category_scrolled_window); + popover_layout_box.append(search_entry); + popover_layout_box.append(separator); + popover_layout_box.append(box_bottom); + flowbox.set_selection_mode(Gtk::SelectionMode::SINGLE); flowbox.set_activate_on_single_click(true); flowbox.set_valign(Gtk::Align::START); @@ -583,28 +715,17 @@ void WayfireMenu::setup_popover_layout() flowbox.set_sort_func(sigc::mem_fun(*this, &WayfireMenu::on_sort)); flowbox.set_filter_func(sigc::mem_fun(*this, &WayfireMenu::on_filter)); flowbox.add_css_class("app-list"); - flowbox.set_size_request(int(menu_min_content_width), int(menu_min_content_height)); flowbox.set_vexpand(true); + flowbox.set_hexpand(true); flowbox_container.append(flowbox); - - scroll_pair.append(category_scrolled_window); - scroll_pair.append(app_scrolled_window); - scroll_pair.set_homogeneous(false); - scroll_pair.set_vexpand(true); - - app_scrolled_window.set_min_content_width(int(menu_min_content_width)); - app_scrolled_window.set_min_content_height(int(menu_min_content_height)); app_scrolled_window.set_child(flowbox_container); app_scrolled_window.add_css_class("app-list-scroll"); app_scrolled_window.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); - app_scrolled_window.set_vexpand(true); category_box.add_css_class("category-list"); category_box.set_orientation(Gtk::Orientation::VERTICAL); - category_scrolled_window.set_min_content_width(int(menu_min_category_width)); - category_scrolled_window.set_min_content_height(int(menu_min_content_height)); category_scrolled_window.set_child(category_box); category_scrolled_window.add_css_class("categtory-list-scroll"); category_scrolled_window.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); @@ -676,31 +797,19 @@ void WayfireMenu::setup_popover_layout() } }, false)); popover_layout_box.add_controller(typing_gesture); + + layout = std::make_shared(this); + popover_layout_box.set_layout_manager(layout); } void WayfireMenu::update_popover_layout() { - /* Layout was already initialized, make sure to remove widgets before - * adding them again */ - auto children = popover_layout_box.get_children(); - if (std::count(children.begin(), children.end(), &search_entry)) - { - popover_layout_box.remove(search_entry); - } - - if (std::count(children.begin(), children.end(), &scroll_pair)) - { - popover_layout_box.remove(scroll_pair); - } - - if (std::count(children.begin(), children.end(), &separator)) + if (!menu_show_categories) { - popover_layout_box.remove(separator); - } - - if (std::count(children.begin(), children.end(), &box_bottom)) + category_scrolled_window.hide(); + } else { - popover_layout_box.remove(box_bottom); + category_scrolled_window.show(); } if (menu_list) @@ -710,25 +819,6 @@ void WayfireMenu::update_popover_layout() { flowbox.set_max_children_per_line(-1); } - - if ((std::string)panel_position == WF_WINDOW_POSITION_TOP) - { - popover_layout_box.append(search_entry); - popover_layout_box.append(scroll_pair); - popover_layout_box.append(separator); - popover_layout_box.append(box_bottom); - } else - { - popover_layout_box.append(scroll_pair); - popover_layout_box.append(search_entry); - popover_layout_box.append(separator); - popover_layout_box.append(box_bottom); - } - - if (!menu_show_categories) - { - category_scrolled_window.hide(); - } } void WayfireLogoutUI::on_logout_click() @@ -942,9 +1032,9 @@ void WayfireMenu::init(Gtk::Box *container) flowbox.set_column_spacing(flowbox_spacing.value()); flowbox.set_column_spacing(flowbox_spacing.value()); }); - menu_min_category_width.set_callback([=] () { update_category_width(); }); - menu_min_content_height.set_callback([=] () { update_content_height(); }); - menu_min_content_width.set_callback([=] () { update_content_width(); }); + menu_min_category_width.set_callback([=] () { update_size(); }); + menu_min_content_height.set_callback([=] () { update_size(); }); + menu_min_content_width.set_callback([=] () { update_size(); }); panel_position.set_callback([=] () { update_popover_layout(); }); menu_show_categories.set_callback([=] () { update_popover_layout(); }); menu_list.set_callback([=] () { update_popover_layout(); }); @@ -1015,22 +1105,38 @@ void WayfireMenu::init(Gtk::Box *container) box.show(); main_image.show(); button->show(); -} -void WayfireMenu::update_category_width() -{ - category_scrolled_window.set_min_content_width(int(menu_min_category_width)); + signals.push_back(button->get_scroll().signal_map().connect([=] () + { + update_size(); + })); } -void WayfireMenu::update_content_height() +void WayfireMenu::update_size() { - category_scrolled_window.set_min_content_height(int(menu_min_content_height)); - app_scrolled_window.set_min_content_height(int(menu_min_content_height)); -} + int width = button->get_scroll().get_width(); + int height = button->get_scroll().get_height(); + if ((width <= 0) || (height <= 0)) + { + /* Not yet allocated, do it next tick */ + width = menu_min_content_width; + height = menu_min_content_height; + if (menu_show_categories.value()) + { + width += menu_min_category_width; + } -void WayfireMenu::update_content_width() -{ - app_scrolled_window.set_min_content_width(int(menu_min_content_width)); + layout->set_limit(width, height); + + Glib::signal_idle().connect_once([=] () + { + update_size(); + }); + return; + } + + /* We know the size of the outside of the scrollbox, use it */ + layout->set_limit(width, height); } void WayfireMenu::toggle_menu() diff --git a/src/panel/widgets/menu.hpp b/src/panel/widgets/menu.hpp index 5d721808..3cbdf5ad 100644 --- a/src/panel/widgets/menu.hpp +++ b/src/panel/widgets/menu.hpp @@ -5,14 +5,36 @@ #include #include -#include "../widget.hpp" -#include "gtkmm/enums.h" -#include "gtkmm/orientable.h" +#include "widget.hpp" #include "wf-popover.hpp" class WayfireMenu; using AppInfo = Glib::RefPtr; +class WfMenuLayout : public Gtk::LayoutManager +{ + protected: + Gtk::Allocation search_alloc, logout_alloc, category_alloc, flow_alloc, separator_alloc; + + void allocate_vfunc(const Gtk::Widget& widget, int width, int height, int baseline) override; + void measure_vfunc(const Gtk::Widget& widget, Gtk::Orientation orientation, int for_size, int& minimum, + int& natural, int& minimum_baseline, int& natural_baseline) const override; + WayfireMenu *menu; + int limit_width = 0, limit_height = 0; + + WfOption show_categories{"panel/menu_show_categories"}; + WfOption category_width{"panel/menu_min_category_width"}; + WfOption content_width{"panel/menu_min_content_width"}; + WfOption content_height{"panel/menu_min_content_height"}; + WfOption panel_position{"panel/position"}; + + public: + WfMenuLayout(WayfireMenu *menu); + + void set_limit(int x, int y); +}; + + class WfMenuCategory { public: @@ -122,9 +144,9 @@ class WayfireMenu : public WayfireWidget { WayfireOutput *output; + public: Gtk::Box flowbox_container; - Gtk::Box box, box_bottom, scroll_pair; - Gtk::Box bottom_pad; + Gtk::Box box, box_bottom; Gtk::Box popover_layout_box; Gtk::Box category_box; Gtk::Separator separator; @@ -134,6 +156,9 @@ class WayfireMenu : public WayfireWidget Gtk::Button logout_button; Gtk::Image logout_image; Gtk::ScrolledWindow app_scrolled_window, category_scrolled_window; + + private: + std::shared_ptr layout; std::unique_ptr button; std::unique_ptr logout_ui; @@ -180,9 +205,7 @@ class WayfireMenu : public WayfireWidget WfOption menu_fullscreen{"panel/menu_fullscreen"}; void setup_popover_layout(); void update_popover_layout(); - void update_category_width(); - void update_content_height(); - void update_content_width(); + void update_size(); void create_logout_ui(); void on_logout_click(); void key_press_search(); diff --git a/src/util/wf-popover.cpp b/src/util/wf-popover.cpp index 63b0f6fe..e9ba429e 100644 --- a/src/util/wf-popover.cpp +++ b/src/util/wf-popover.cpp @@ -193,6 +193,27 @@ WayfireMenuWidget::WayfireMenuWidget(const std::string& section, const std::stri popover.set_autohide(false); + auto panel_position_changed = [=] () + { + auto pos = panel_position.value(); + if (pos == "top") + { + popover.set_position(Gtk::PositionType::BOTTOM); + } else if (pos == "bottom") + { + popover.set_position(Gtk::PositionType::TOP); + } else if (pos == "left") + { + popover.set_position(Gtk::PositionType::RIGHT); + } else if (pos == "right") + { + popover.set_position(Gtk::PositionType::LEFT); + } + }; + + panel_position_changed(); + panel_position.set_callback(panel_position_changed); + gtk_widget_set_parent(GTK_WIDGET(popover.gobj()), GTK_WIDGET(this->gobj())); gtk_widget_set_parent(GTK_WIDGET(menu.gobj()), GTK_WIDGET(this->gobj())); /* Moved to another menu */