diff --git a/include/bar.hpp b/include/bar.hpp index ee4a1d5e..d2cbffa0 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -12,6 +12,7 @@ #include #include "AModule.hpp" +#include "group.hpp" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar { @@ -101,7 +102,7 @@ class Bar { private: void onMap(GdkEventAny *); auto setupWidgets() -> void; - void getModules(const Factory &, const std::string &, Gtk::Box *); + void getModules(const Factory &, const std::string &, waybar::Group *); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); void setMode(const bar_mode &); diff --git a/include/group.hpp b/include/group.hpp index 60e31c96..67cf4385 100644 --- a/include/group.hpp +++ b/include/group.hpp @@ -5,18 +5,31 @@ #include #include "AModule.hpp" -#include "bar.hpp" -#include "factory.hpp" +#include "gtkmm/revealer.h" namespace waybar { class Group : public AModule { public: Group(const std::string&, const std::string&, const Json::Value&, bool); - ~Group() = default; + virtual ~Group() = default; auto update() -> void override; operator Gtk::Widget&() override; + + virtual Gtk::Box& getBox(); + void addWidget(Gtk::Widget& widget); + + bool handleMouseHover(GdkEventCrossing* const& e); + + protected: Gtk::Box box; + Gtk::Box revealer_box; + Gtk::Revealer revealer; + bool is_first_widget = true; + bool is_drawer = false; + std::string add_class_to_drawer_children; + + void addHoverHandlerTo(Gtk::Widget& widget); }; } // namespace waybar diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 92b365d9..9c3220f8 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -240,7 +240,7 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270. ## Grouping modules -Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. +Module groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the "orientation" property. A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example: @@ -263,6 +263,43 @@ A module group is defined by specifying a module named "group/some-group-name". Valid options for the (optional) "orientation" property are: "horizontal", "vertical", "inherit", and "orthogonal" (default). +## Group Drawers + +A group may hide all but one element, showing them only on mouse hover. In order to configure this, you can use the `drawer` property, whose value is an object with the following properties: + +*transition-duration*: ++ + typeof: integer ++ + default: 500 ++ + Defines the duration of the transition animation in milliseconds. + +*children-class*: ++ + typeof: string ++ + default: "hidden" ++ + Defines the CSS class to be applied to the hidden elements. + +*transition-left-to-right*: ++ + typeof: bool ++ + default: true ++ + Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left. + When the bar is vertical, it reads as top-to-bottom. + +``` +"group/power": { + "orientation": "inherit", + "drawer": { + "transition-duration": 500, + "children-class": "not-power", + "transition-left-to-right": false, + }, + "modules": [ + "custom/power", // First element is the "group leader" and won't ever be hidden + "custom/quit", + "custom/lock", + "custom/reboot", + ] +}, +``` + # SUPPORTED MODULES - *waybar-backlight(5)* diff --git a/src/bar.cpp b/src/bar.cpp index 30cf7fad..d0a187c6 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -740,7 +740,7 @@ void waybar::Bar::handleSignal(int signal) { } void waybar::Bar::getModules(const Factory& factory, const std::string& pos, - Gtk::Box* group = nullptr) { + waybar::Group* group = nullptr) { auto module_list = group ? config[pos]["modules"] : config[pos]; if (module_list.isArray()) { for (const auto& name : module_list) { @@ -753,10 +753,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, auto id_name = ref.substr(6, hash_pos - 6); auto class_name = hash_pos != std::string::npos ? ref.substr(hash_pos + 1) : ""; - auto parent = group ? group : &this->box_; - auto vertical = parent->get_orientation() == Gtk::ORIENTATION_VERTICAL; + auto vertical = (group ? group->getBox().get_orientation() : box_.get_orientation()) == + Gtk::ORIENTATION_VERTICAL; + auto group_module = new waybar::Group(id_name, class_name, config[ref], vertical); - getModules(factory, ref, &group_module->box); + getModules(factory, ref, group_module); module = group_module; } else { module = factory.makeModule(ref); @@ -765,7 +766,7 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, std::shared_ptr module_sp(module); modules_all_.emplace_back(module_sp); if (group) { - group->pack_start(*module, false, false); + group->addWidget(*module); } else { if (pos == "modules-left") { modules_left_.emplace_back(module_sp); diff --git a/src/group.cpp b/src/group.cpp index 548fb0da..cad36e51 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -4,12 +4,32 @@ #include +#include "gdkmm/device.h" +#include "gtkmm/widget.h" + namespace waybar { +const Gtk::RevealerTransitionType getPreferredTransitionType(bool is_vertical, bool left_to_right) { + if (is_vertical) { + if (left_to_right) { + return Gtk::RevealerTransitionType::REVEALER_TRANSITION_TYPE_SLIDE_DOWN; + } else { + return Gtk::RevealerTransitionType::REVEALER_TRANSITION_TYPE_SLIDE_UP; + } + } else { + if (left_to_right) { + return Gtk::RevealerTransitionType::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT; + } else { + return Gtk::RevealerTransitionType::REVEALER_TRANSITION_TYPE_SLIDE_LEFT; + } + } +} + Group::Group(const std::string& name, const std::string& id, const Json::Value& config, bool vertical) - : AModule(config, name, id, false, false), - box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { + : AModule(config, name, id, true, true), + box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, + revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { box.set_name(name_); if (!id.empty()) { box.get_style_context()->add_class(id); @@ -29,12 +49,77 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value& } else { throw std::runtime_error("Invalid orientation value: " + orientation); } + + if (config_["drawer"].isObject()) { + is_drawer = true; + + const auto& drawer_config = config_["drawer"]; + const int transition_duration = + (drawer_config["transition-duration"].isInt() ? drawer_config["transition-duration"].asInt() + : 500); + add_class_to_drawer_children = + (drawer_config["children-class"].isString() ? drawer_config["children-class"].asString() + : "drawer-child"); + const bool left_to_right = (drawer_config["transition-left-to-right"].isBool() + ? drawer_config["transition-left-to-right"].asBool() + : true); + + auto transition_type = getPreferredTransitionType(vertical, left_to_right); + + revealer.set_transition_type(transition_type); + revealer.set_transition_duration(transition_duration); + revealer.set_reveal_child(false); + + revealer.get_style_context()->add_class("drawer"); + + revealer.add(revealer_box); + box.pack_start(revealer); + + addHoverHandlerTo(revealer); + } +} + +bool Group::handleMouseHover(GdkEventCrossing* const& e) { + switch (e->type) { + case GDK_ENTER_NOTIFY: + revealer.set_reveal_child(true); + break; + case GDK_LEAVE_NOTIFY: + revealer.set_reveal_child(false); + break; + default: + break; + } + + return true; +} + +void Group::addHoverHandlerTo(Gtk::Widget& widget) { + widget.add_events(Gdk::EventMask::ENTER_NOTIFY_MASK | Gdk::EventMask::LEAVE_NOTIFY_MASK); + widget.signal_enter_notify_event().connect(sigc::mem_fun(*this, &Group::handleMouseHover)); + widget.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Group::handleMouseHover)); } auto Group::update() -> void { // noop } +Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; } + +void Group::addWidget(Gtk::Widget& widget) { + getBox().pack_start(widget, false, false); + + if (is_drawer) { + // Necessary because of GTK's hitbox detection + addHoverHandlerTo(widget); + if (!is_first_widget) { + widget.get_style_context()->add_class(add_class_to_drawer_children); + } + } + + is_first_widget = false; +} + Group::operator Gtk::Widget&() { return box; } } // namespace waybar