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 4
std::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 one
if(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 future
header = 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 back
this->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 option
auto 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 window
this->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 option
auto 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/hidden
active_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 buttons
auto 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);