roundabout,
created on Thursday, 6 March 2025, 11:16:12 (1741259772),
received on Thursday, 6 March 2025, 11:16:14 (1741259774)
Author identity: vlad <vlad.muntoiu@gmail.com>
8271145c952fb5f79ab607569b8977ee02498e21
gpanthera.cc
@@ -8,6 +8,7 @@
namespace gPanthera { std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget) { // Get a vector of the children of a GTK widget, since the container API was removed in GTK 4std::vector<Gtk::Widget*> children; for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) { children.push_back(child);
@@ -16,6 +17,7 @@ namespace gPanthera {
} Gtk::Image *copy_image(Gtk::Image *image) { // Generate a new Gtk::Image with the same contents as an existing oneif(image->get_storage_type() == Gtk::Image::Type::PAINTABLE) { return Gtk::make_managed<Gtk::Image>(image->get_paintable()); } else if(image->get_storage_type() == Gtk::Image::Type::ICON_NAME) {
@@ -45,6 +47,7 @@ namespace gPanthera {
} this->layout = std::move(layout); this->label.set_text(label); // This should be replaced with a custom class in the futureheader = std::make_unique<Gtk::HeaderBar>(); header->set_show_title_buttons(false); if(custom_header) {
@@ -117,6 +120,7 @@ namespace gPanthera {
void DockablePane::redock(DockStack *stack) { if(this->window != nullptr) { this->window->hide(); // Put the titlebar backthis->window->unset_titlebar(); this->window->set_decorated(false); this->header->get_style_context()->remove_class("titlebar");
@@ -133,6 +137,7 @@ namespace gPanthera {
this->window->destroy(); delete this->window; this->window = nullptr; // Re-enable the pop out optionauto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out")); action->set_enabled(true); this->header->get_style_context()->remove_class("gpanthera-dock-titlebar-popout");
@@ -146,11 +151,13 @@ namespace gPanthera {
} if(this->window == nullptr) { // Remove the header bar from the pane, so it can be used as the titlebar of the windowthis->remove(*this->header); this->window = new DockWindow(this); this->window->set_titlebar(*this->header); this->window->set_child(*this); this->window->set_decorated(true); // Grey out the pop-out optionauto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out")); action->set_enabled(false); this->header->get_style_context()->add_class("gpanthera-dock-titlebar-popout");
@@ -185,6 +192,14 @@ namespace gPanthera {
stacks.push_back(stack); } void LayoutManager::remove_pane(DockablePane *pane) { panes.erase(std::ranges::remove(panes, pane).begin(), panes.end()); } void LayoutManager::remove_stack(DockStack *stack) { stacks.erase(std::ranges::remove(stacks, stack).begin(), stacks.end()); } DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) { auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); add(*empty_child, "empty");
@@ -200,6 +215,13 @@ namespace gPanthera {
} }); // Also hide when the visible child is removed this->signal_child_removed.connect([this](Gtk::Widget* const &child) { if(this->get_visible_child_name() == "" || this->get_visible_child_name() == "empty") { this->hide(); } }); this->set_visible_child("empty"); this->hide(); }
@@ -221,6 +243,20 @@ namespace gPanthera {
stack->signal_child_added.connect(update_callback); stack->signal_child_removed.connect(update_callback); this->get_style_context()->add_class("gpanthera-dock-switcher"); drop_target = Gtk::DropTarget::create(DockablePane::get_type(), Gdk::DragAction::MOVE); this->add_controller(drop_target); // Process dropped buttons drop_target->signal_drop().connect([this](const Glib::ValueBase& value, double x, double y) { const auto &widget = static_cast<const Glib::Value<Gtk::Widget*>&>(value).get(); if(widget) { if(auto pane = dynamic_cast<DockablePane*>(widget)) { this->stack->add_pane(*pane); } } return true; // Drop OK }, false);} DockStackSwitcher::~DockStackSwitcher() {
@@ -237,15 +273,42 @@ namespace gPanthera {
this->set_valign(Gtk::Align::CENTER); this->get_style_context()->add_class("toggle"); this->get_style_context()->add_class("gpanthera-dock-button"); // Add/remove CSS classes when the pane is shown/hiddenactive_style_handler = this->pane->get_stack()->property_visible_child_name().signal_changed().connect([this]() { if(this->pane->get_stack()->get_visible_child_name() == this->pane->get_identifier()) {this->add_css_class("checked");this->add_css_class("gpanthera-dock-button-active");this->update_active_style(); }); drag_source = Gtk::DragSource::create(); drag_source->set_exclusive(false); // This is to prevent the click handler from taking over grabbing the button drag_source->set_propagation_phase(Gtk::PropagationPhase::CAPTURE); value = Glib::Value<Gtk::Widget*>(); value.init(DockablePane::get_type()); value.set(pane); // Add the drag source to the button this->add_controller(drag_source); this->signal_clicked().connect([this, pane]() { if(pane->get_stack()->get_visible_child_name() == pane->get_identifier()) { pane->get_stack()->set_visible_child("empty");} else { this->remove_css_class("checked");this->remove_css_class("gpanthera-dock-button-active");pane->get_stack()->set_visible_child(pane->get_identifier());} }); // Provide the drag data drag_source->signal_prepare().connect([this](double, double) { drag_source->set_actions(Gdk::DragAction::MOVE); return Gdk::ContentProvider::create(value); }, false); this->update_active_style(); } void DockButton::update_active_style() { if(this->pane->get_stack()->get_visible_child_name() == this->pane->get_identifier()) { this->add_css_class("checked"); this->add_css_class("gpanthera-dock-button-active"); } else { this->remove_css_class("checked"); this->remove_css_class("gpanthera-dock-button-active"); }} DockButton::~DockButton() {
@@ -253,6 +316,7 @@ namespace gPanthera {
} void DockStackSwitcher::update_buttons() { // Clear the old buttonsauto old_buttons = collect_children(*this); for(auto *button : old_buttons) { remove(*button);
@@ -260,13 +324,6 @@ namespace gPanthera {
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) { if(auto pane = dynamic_cast<DockablePane*>(widget)) { auto *button = Gtk::make_managed<DockButton>(pane); button->signal_clicked().connect([this, pane]() {if(stack->get_visible_child_name() == pane->get_identifier()) {stack->set_visible_child("empty");} else {stack->set_visible_child(pane->get_identifier());}});if(pane->get_identifier() != Glib::ustring("empty")) { append(*button); }
gpanthera.hh
@@ -55,6 +55,8 @@ namespace gPanthera {
void add_pane(DockablePane *pane); void add_stack(DockStack *stack); void remove_pane(DockablePane *pane); void remove_stack(DockStack *stack);}; class DockStack : public Gtk::Stack {
@@ -76,6 +78,9 @@ namespace gPanthera {
class DockButton : public Gtk::Button { private: sigc::connection active_style_handler; std::shared_ptr<Gtk::DragSource> drag_source; Glib::Value<Gtk::Widget*> value; void update_active_style();public: DockablePane *pane; explicit DockButton(DockablePane *pane);
@@ -86,6 +91,7 @@ namespace gPanthera {
private: DockStack *stack; sigc::connection add_handler, remove_handler; std::shared_ptr<Gtk::DropTarget> drop_target;public: void update_buttons(); explicit DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL);