diff --git a/Cargo.lock b/Cargo.lock index 2dd68f7..ca905f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,9 +1974,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -2895,6 +2895,7 @@ name = "terms" version = "0.1.0" dependencies = [ "anyhow", + "approx", "ashpd", "async-channel 2.1.1", "async-std", @@ -2914,6 +2915,7 @@ dependencies = [ "libadwaita", "libc", "librsvg", + "num-traits", "once_cell", "pango", "rand", diff --git a/Cargo.toml b/Cargo.toml index 941959e..f609e20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,8 @@ libc = "0.2.153" thiserror = "1.0.56" anyhow = "1.0.79" zbus = "3.14.1" +num-traits = "0.2.18" +approx = "0.5.1" [build-dependencies] glib-build-tools = "0.19.0" diff --git a/data/resources/meson.build b/data/resources/meson.build index b260fa7..7ffdb3f 100644 --- a/data/resources/meson.build +++ b/data/resources/meson.build @@ -1,6 +1,30 @@ # Resources +fs = import('fs') subdir('icons') +# stylesheet_deps = [] +# sassc = find_program('sassc') + +# if sassc.found() +# sassc_opts = [ '-a', '-M', '-t', 'compact' ] + +# scss_files = [ +# 'style', +# 'style-dark', +# ] + +# foreach scss: scss_files +# stylesheet_deps += custom_target('@0@.scss'.format(scss), +# input: '@0@.scss'.format(scss), +# output: '@0@.css'.format(scss), +# command: [ +# sassc, sassc_opts, '@INPUT@', '@OUTPUT@', +# ], +# ) +# endforeach +# endif + + resources = gnome.compile_resources( 'resources', 'resources.gresource.xml', diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index 595fac8..e13ee1a 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -34,6 +34,7 @@ + ../../src/twl/panel_header.ui ../../src/twl/zoom_controls.ui ../../src/twl/style_switcher.ui diff --git a/data/resources/style.css b/data/resources/style.css index 151e49f..bc0b7c9 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -3,6 +3,7 @@ @define-color root_context_color shade(@red_1, 1.38); @define-color ssh_context_color shade(@purple_1, 1.28); +/* @define-color panel_border_color alpha(@shade_color, 1); */ /* terms_main_windowwith-borders:not(.fullscreen):backdrop { border-color: alpha(@borders, 0.5); @@ -22,6 +23,7 @@ window:not(.about) headerbar, margin-bottom: -6px; } + /* #terms_main_window.context-root .custom-headerbar { background-color: @root_context_color; } @@ -83,11 +85,20 @@ window:not(.about) headerbar, padding: 12px; } -#twl_style_switcher { + + +/******************************** TWL ************************************/ + +panel_grid paned separator { + background-color: @headerbar_bg_color; + border-color: @headerbar_bg_color; +} + +style_switcher { padding: 6px; } -#twl_style_switcher .check { +style_switcher .check { background: @accent_bg_color; color: @accent_fg_color; padding: 2px; @@ -97,12 +108,12 @@ window:not(.about) headerbar, /* Adapted from https://gitlab.gnome.org/GNOME/gnome-text-editor/-/blob/bf8c0c249f06a0be69e65aed3b786ba02a9f999e/src/TextEditor.css#L51 */ -#twl_style_switcher checkbutton { +style_switcher checkbutton { outline-offset: 1px; transition: none; } -#twl_style_switcher checkbutton radio { +style_switcher checkbutton radio { -gtk-icon-source: none; background: none; padding: 12px; @@ -114,25 +125,37 @@ window:not(.about) headerbar, box-shadow: inset 0 0 0 1px @borders; } -#twl_style_switcher checkbutton radio:checked { +style_switcher checkbutton radio:checked { box-shadow: inset 0 0 0 2px @accent_bg_color; } -#twl_style_switcher checkbutton.system radio { +style_switcher checkbutton.system radio { background: linear-gradient(-45deg, #1e1e1e 49.99%, white 50.01%); } -#twl_style_switcher checkbutton.light radio { +style_switcher checkbutton.light radio { color: alpha(black, 0.8); background-color: white; } -#twl_style_switcher checkbutton.dark radio { +style_switcher checkbutton.dark radio { color: white; background-color: #1e1e1e; } -paned separator { - background-color: @window_bg_color; - border-color: @window_bg_color; + +panel_header { + -gtk-icon-size: 12px; +} + + +panel_header.toolbar { + padding: 0px; + border-spacing: 0px; +} + + +panel_header button { + min-height: 16px; + min-width: 16px; } diff --git a/src/components/terminal_tab/terminal_tab.rs b/src/components/terminal_tab/terminal_tab.rs index 6b16a93..d56cc40 100644 --- a/src/components/terminal_tab/terminal_tab.rs +++ b/src/components/terminal_tab/terminal_tab.rs @@ -127,6 +127,11 @@ impl TerminalTab { })); self.panel_grid.set_wide_handle(self.settings.use_wide_panel_resize_handle()); + self.settings.connect_show_panel_headers_changed(clone!(@weak self as this => move |s| { + this.panel_grid.set_show_panel_headers(s.show_panel_headers()); + })); + self.panel_grid.set_show_panel_headers(self.settings.show_panel_headers()); + self.panel_grid.connect_selected_panel_notify(clone!(@weak self as this => move |s| { this.on_selected_panel_change(); })); @@ -163,7 +168,7 @@ impl TerminalTab { } fn get_selected(&self) -> Option { - self.panel_grid.selected_panel().and_then(|p| p.child()).and_downcast() + self.panel_grid.selected_panel().and_then(|p| p.content()).and_downcast() } fn set_selected(&self, terminal: Option) { @@ -176,7 +181,7 @@ impl TerminalTab { debug!("on panel changed: {:?}", panel); self.selected_panel_signals.set_target(panel.as_ref()); if let Some(panel) = panel.as_ref() { - let term = panel.child().and_downcast::(); + let term = panel.content().and_downcast::(); debug!("Set active term {:?}", term); self.active_term_signals.set_target(term.as_ref()); if let Some(term) = term.as_ref() { @@ -188,7 +193,7 @@ impl TerminalTab { pub fn split(&self, orientation: Option) { let term = Terminal::new(self.directory.borrow().clone(), self.command.borrow().clone(), self.env.borrow().clone()); - term.grab_focus(); + // term.grab_focus(); let panel = self.panel_grid.split(&term, orientation); self.connect_terminal_signals(&term, &panel); @@ -201,14 +206,14 @@ impl TerminalTab { })); terminal.connect_title_notify(clone!(@weak self as this, @weak panel as panel => move |term| { - panel.set_title(term.title()); + panel.header().set_title(term.title()); })); } pub fn on_panel_close_request(&self, panel: &Panel) -> glib::Propagation { info!("on_panel_close_request: {:?}", panel); // TODO: test if process is still running - if let Some(terminal) = panel.child().and_downcast_ref::() {} + if let Some(terminal) = panel.content().and_downcast_ref::() {} glib::Propagation::Proceed } diff --git a/src/twl/bin.rs b/src/twl/bin.rs new file mode 100644 index 0000000..458cbac --- /dev/null +++ b/src/twl/bin.rs @@ -0,0 +1,22 @@ +use glib::{prelude::*, subclass::prelude::*}; + +use super::{bin_imp as imp, utils::TwlWidgetExt}; + +glib::wrapper! { + pub struct Bin(ObjectSubclass) + @extends gtk::Widget, adw::Bin, + @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; +} + +impl Bin { + pub fn new(child: &impl IsA) -> Self { + glib::Object::builder().property("child", child).build() + } +} + +impl Default for Bin { + fn default() -> Self { + glib::Object::builder().build() + } +} +impl TwlWidgetExt for Bin {} diff --git a/src/twl/bin_imp.rs b/src/twl/bin_imp.rs new file mode 100644 index 0000000..c5b6024 --- /dev/null +++ b/src/twl/bin_imp.rs @@ -0,0 +1,374 @@ +use std::cmp::Ordering; + +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib; +use gtk::graphene; + +use approx::abs_diff_eq; +use num_traits as num; + +use super::utils::{Orthogonal, TwlWidgetExt}; + +#[derive(Debug, Default)] +pub struct Bin {} + +#[glib::object_subclass] +impl ObjectSubclass for Bin { + const NAME: &'static str = "TwlBin"; + type Type = super::Bin; + type ParentType = adw::Bin; +} + +impl ObjectImpl for Bin { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for Bin { + fn focus(&self, direction: gtk::DirectionType) -> bool { + let focus_child = self.obj().focus_child(); + + let mut ret = false; + for child in self.focus_sort(direction.clone()).into_iter() { + if focus_child.as_ref() == Some(&child) { + ret = child.child_focus(direction.clone()); + } else if child.is_mapped() && child.is_ancestor(&*self.obj()) { + ret = child.child_focus(direction.clone()); + } + } + ret + } + + fn grab_focus(&self) -> bool { + for child in self.obj().iter_children() { + if child.grab_focus() { + return true; + } + } + false + } +} + +impl BinImpl for Bin {} + +impl Bin { + fn old_focus_coords(&self) -> Option { + self.obj() + .root() + .and_then(|r| r.focus()) + .and_then(|old_focus| old_focus.compute_bounds(self.obj().as_ref())) + } + + /// Look for a child in @children that is intermediate between the focus widget + /// and container. This widget, if it exists, acts as the starting widget for + /// focus navigation. + fn find_old_focus(&self, children: &mut Vec) -> Option { + for child in children { + let mut test_child = child.clone(); + let mut found = true; + while let Some(parent) = test_child.parent() { + if parent == *self.obj() { + break; + } + + if let Some(focus_child) = parent.focus_child() { + if focus_child != *self.obj() { + found = false; + break; + } + } + + test_child = parent; + } + + if found { + return Some(child.clone()); + } + } + + None + } + + pub fn focus_sort_tab(&self, children: &mut Vec, direction: gtk::DirectionType) { + let text_direction = self.obj().direction(); + children.sort_by(|child1, child2| { + let child_bounds1 = child1.parent().and_then(|p1| child1.compute_bounds(&p1)); + let child_bounds2 = child2.parent().and_then(|p2| child1.compute_bounds(&p2)); + + if child_bounds1.is_none() || child_bounds2.is_none() { + return Ordering::Equal; + } + + let child_bounds1 = child_bounds1.unwrap(); + let child_bounds2 = child_bounds2.unwrap(); + + let y1 = child_bounds1.y() as f64 + (child_bounds1.height() as f64 / 2.0); + let y2 = child_bounds2.y() as f64 + (child_bounds2.height() as f64 / 2.0); + + if abs_diff_eq!(y1, y2) { + let x1 = child_bounds1.x() as f64 + (child_bounds1.width() as f64 / 2.0); + let x2 = child_bounds2.x() as f64 + (child_bounds2.width() as f64 / 2.0); + + let mut inv = if text_direction == gtk::TextDirection::Rtl { -1 } else { 1 }; + + if direction == gtk::DirectionType::TabBackward { + inv = inv * -1; + } + + let ordering = if x1 < x2 { + -1 * inv + } else if abs_diff_eq!(x1, x2) { + 0 + } else { + inv + }; + + ordering.cmp(&0) + } else { + let mut ordering = if y1 < y2 { -1 } else { 1 }; + + if direction == gtk::DirectionType::TabBackward { + ordering = ordering * -1; + } + ordering.cmp(&0) + } + }) + } + + pub fn focus_sort_left_right(&self, children: &mut Vec, direction: gtk::DirectionType) { + let old_focus = self.obj().focus_child().or_else(|| self.find_old_focus(children)); + + let old_bounds = old_focus.as_ref().and_then(|w| w.compute_bounds(self.obj().as_ref())); + + let (compare_x, compare_y) = if let (Some(old_focus), Some(old_bounds)) = (old_focus, old_bounds) { + // Delete widgets from list that don't match minimum criteria + let compare_y1 = old_bounds.y(); + let compare_y2 = old_bounds.y() + old_bounds.height(); + + let compare_x = if direction == gtk::DirectionType::Left { + old_bounds.x() + } else { + old_bounds.x() + old_bounds.width() + }; + + *children = children + .iter() + .filter(|child| { + if *child != &old_focus { + if let Some(child_bounds) = child.compute_bounds(self.obj().as_ref()) { + let child_y1 = child_bounds.y(); + let child_y2 = child_bounds.y() + child_bounds.height(); + + if abs_diff_eq!(child_y2, compare_y1) || child_y2 < compare_y1 || + abs_diff_eq!(child_y1, compare_y2) || child_y1 > compare_y2 /* No vertical overlap */ || + (direction == gtk::DirectionType::Right && (child_bounds.x() + child_bounds.width()) < compare_x) || /* Not to left */ + (direction == gtk::DirectionType::Left && (child_bounds.x() > compare_x)) + /* Not to right */ + { + return false; + } + } else { + return false; + } + } + true + }) + .cloned() + .collect(); + (old_bounds.x() + (old_bounds.width() / 2.0), (compare_y1 + compare_y2) / 2.0) + } else { + // No old focus widget, need to figure out starting x,y some other way + + let bounds = self + .obj() + .compute_bounds(self.obj().parent().as_ref().unwrap_or(self.obj().upcast_ref())) + .unwrap_or(graphene::Rect::new(0.0, 0.0, 0.0, 0.0)); + let compare_y = if let Some(old_focus_bounds) = self.old_focus_coords() { + old_focus_bounds.y() + (old_focus_bounds.height() / 2.0) + } else if self.obj().native().is_none() { + bounds.y() + (bounds.height() / 2.0) + } else { + bounds.height() / 2.0 + }; + + let compare_x = if self.obj().native().is_none() { + if direction == gtk::DirectionType::Right { + bounds.x() + } else { + bounds.x() + bounds.width() + } + } else { + if direction == gtk::DirectionType::Left { + 0.0 + } else { + bounds.width() + } + }; + + (compare_x, compare_y) + }; + + let reverse = direction == gtk::DirectionType::Left; + + children.sort_by(|child1, child2| self.axis_compare(child1, child2, compare_x, compare_y, reverse, gtk::Orientation::Horizontal)) + } + + pub fn focus_sort_up_down(&self, children: &mut Vec, direction: gtk::DirectionType) { + let old_focus = self.obj().focus_child().or_else(|| self.find_old_focus(children)); + + let old_bounds = old_focus.as_ref().and_then(|w| w.compute_bounds(self.obj().as_ref())); + let (compare_x, compare_y) = if let (Some(old_focus), Some(old_bounds)) = (old_focus, old_bounds) { + // Delete widgets from list that don't match minimum criteria + let compare_x1 = old_bounds.x(); + let compare_x2 = old_bounds.x() + old_bounds.width(); + + let compare_y = if direction == gtk::DirectionType::Up { + old_bounds.y() + } else { + old_bounds.y() + old_bounds.height() + }; + + *children = children + .iter() + .filter(|child| { + if *child != &old_focus { + if let Some(child_bounds) = child.compute_bounds(self.obj().as_ref()) { + let child_x1 = child_bounds.x(); + let child_x2 = child_bounds.x() + child_bounds.width(); + + if abs_diff_eq!(child_x2, compare_x1) || child_x2 < compare_x1 || + abs_diff_eq!(child_x1, compare_x2) || child_x1 > compare_x2 /* No horizontal overlap */ || + (direction == gtk::DirectionType::Down && (child_bounds.y() + child_bounds.height()) < compare_y) || /* Not below */ + (direction == gtk::DirectionType::Up && (child_bounds.y() > compare_y)) + /* Not above */ + { + return false; + } + } else { + return false; + } + } + true + }) + .cloned() + .collect(); + ((compare_x1 + compare_x2) / 2.0, old_bounds.y() + (old_bounds.height() / 2.0)) + } else { + // No old focus widget, need to figure out starting x,y some other way + + let bounds = self + .obj() + .compute_bounds(self.obj().parent().as_ref().unwrap_or(self.obj().upcast_ref())) + .unwrap_or(graphene::Rect::new(0.0, 0.0, 0.0, 0.0)); + let compare_x = if let Some(old_focus_bounds) = self.old_focus_coords() { + old_focus_bounds.x() + (old_focus_bounds.width() / 2.0) + } else if self.obj().native().is_none() { + bounds.x() + (bounds.width() / 2.0) + } else { + bounds.width() / 2.0 + }; + + let compare_y = if self.obj().native().is_none() { + if direction == gtk::DirectionType::Down { + bounds.y() + } else { + bounds.y() + bounds.height() + } + } else { + if direction == gtk::DirectionType::Down { + 0.0 + } else { + bounds.height() + } + }; + + (compare_x, compare_y) + }; + + let reverse = direction == gtk::DirectionType::Up; + + children.sort_by(|child1, child2| self.axis_compare(child1, child2, compare_x, compare_y, reverse, gtk::Orientation::Vertical)) + } + + fn focus_sort(&self, direction: gtk::DirectionType) -> Vec { + // Initialize the list with all visible child widgets + let mut children: Vec = self.obj().iter_children().filter(|c| c.is_mapped() && c.is_sensitive()).collect(); + + // Now sort that list depending on @direction + match direction { + gtk::DirectionType::TabForward | gtk::DirectionType::TabBackward => self.focus_sort_tab(&mut children, direction), + gtk::DirectionType::Up | gtk::DirectionType::Down => self.focus_sort_up_down(&mut children, direction), + gtk::DirectionType::Left | gtk::DirectionType::Right => self.focus_sort_left_right(&mut children, direction), + _ => unreachable!("unknown direction type"), + } + + children + } + + fn axis_compare( + &self, + child1: &impl IsA, + child2: &impl IsA, + x: f32, + y: f32, + reverse: bool, + orientation: gtk::Orientation, + ) -> Ordering { + let bounds1 = child1.as_ref().compute_bounds(self.obj().as_ref()); + let bounds2 = child2.as_ref().compute_bounds(self.obj().as_ref()); + + if bounds1.is_none() || bounds2.is_none() { + return Ordering::Equal; + } + + let (mut start1, end1) = axis_info(bounds1.as_ref().unwrap(), orientation); + let (mut start2, end2) = axis_info(bounds2.as_ref().unwrap(), orientation); + + start1 = start1 + (end1 / 2.0); + start2 = start2 + (end2 / 2.0); + + let (x1, x2) = if start1 == start2 { + // Now use origin/bounds to compare the 2 widgets on the other axis + let (start1, end1) = axis_info(bounds1.as_ref().unwrap(), orientation.orthogonal()); + let (start2, end2) = axis_info(bounds2.as_ref().unwrap(), orientation.orthogonal()); + + let x1 = num::abs(start1 + (end1 / 2.0) - x); + let x2 = num::abs(start2 + (end2 / 2.0) - x); + + (x1, x2) + } else { + (start1, start2) + }; + + let inv = if reverse { -1 } else { 1 }; + let ordering = if x1 < x2 { + -1 * inv + } else if abs_diff_eq!(x1, x2) { + 0 + } else { + inv + }; + ordering.cmp(&0) + } + + // gboolean + // adw_widget_grab_focus_self (GtkWidget *widget) + // { + // if (!gtk_widget_get_focusable (widget)) + // return FALSE; + + // gtk_root_set_focus (gtk_widget_get_root (widget), widget); + + // return TRUE; + // } +} + +fn axis_info(bounds: &graphene::Rect, orientation: gtk::Orientation) -> (f32, f32) { + match orientation { + gtk::Orientation::Horizontal => (bounds.x(), bounds.width()), + gtk::Orientation::Vertical => (bounds.y(), bounds.height()), + _ => unreachable!(), + } +} diff --git a/src/twl/fading_label.rs b/src/twl/fading_label.rs new file mode 100644 index 0000000..5c58fe8 --- /dev/null +++ b/src/twl/fading_label.rs @@ -0,0 +1,26 @@ +use glib::{prelude::*, subclass::prelude::*}; + +use super::{fading_label_imp as imp, utils::TwlWidgetExt}; + +glib::wrapper! { + pub struct FadingLabel(ObjectSubclass) + @extends gtk::Widget, + @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; +} + +impl FadingLabel { + pub fn new(label: Option<&str>) -> Self { + let mut builder = glib::Object::builder(); + if let Some(label) = label { + builder = builder.property("label", label); + } + builder.build() + } +} + +impl Default for FadingLabel { + fn default() -> Self { + glib::Object::builder().build() + } +} +impl TwlWidgetExt for FadingLabel {} diff --git a/src/twl/fading_label_imp.rs b/src/twl/fading_label_imp.rs new file mode 100644 index 0000000..73cf6d0 --- /dev/null +++ b/src/twl/fading_label_imp.rs @@ -0,0 +1,195 @@ +use std::{cell::Cell, marker::PhantomData}; + +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib::{self, Properties}; +use gtk::{graphene, gsk}; + +use approx::abs_diff_eq; +use num_traits as num; + +const DEFAULT_FADE_WIDTH: f32 = 18.0; + +#[derive(Debug, Properties)] +#[properties(wrapper_type=super::FadingLabel)] +pub struct FadingLabel { + #[property(get=Self::get_label, set=Self::set_label, construct, explicit_notify)] + label: PhantomData, + + #[property(get, set=Self::set_align, minimum=0.0, maximum=1.0, default=0.0, construct, explicit_notify)] + align: Cell, + + #[property(get, set=Self::set_fade_width, default=DEFAULT_FADE_WIDTH, construct, explicit_notify)] + fade_width: Cell, + + label_widget: gtk::Label, +} + +impl Default for FadingLabel { + fn default() -> Self { + Self { + label_widget: gtk::Label::new(None), + label: Default::default(), + align: Cell::new(0.0), + fade_width: Cell::new(DEFAULT_FADE_WIDTH), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for FadingLabel { + const NAME: &'static str = "TwlFadingLabel"; + type Type = super::FadingLabel; + type ParentType = gtk::Widget; +} + +#[glib::derived_properties] +impl ObjectImpl for FadingLabel { + fn constructed(&self) { + self.parent_constructed(); + + self.label_widget.set_parent(&*self.obj()); + self.label_widget.set_single_line_mode(true); + } + + fn dispose(&self) { + self.label_widget.unparent(); + } +} + +impl WidgetImpl for FadingLabel { + fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) { + let (mut min, nat, min_baseline, nat_baseline) = self.label_widget.measure(orientation, for_size); + + if orientation == gtk::Orientation::Horizontal && min > 0 { + min = 0; + } + (min, nat, min_baseline, nat_baseline) + } + + fn size_allocate(&self, width: i32, height: i32, baseline: i32) { + let align = if self.is_rtl() { 1.0 - self.align.get() } else { self.align.get() }; + + let (_, child_width, _, _) = self.label_widget.measure(gtk::Orientation::Horizontal, height); + + let offset = (width as f32 - child_width as f32) * align; + let transform = gsk::Transform::new().translate(&graphene::Point::new(offset, 0.0)); + + self.label_widget.allocate(child_width, height, baseline, Some(transform)); + } + + fn snapshot(&self, snapshot: >k::Snapshot) { + let align = if self.is_rtl() { 1.0 - self.align.get() } else { self.align.get() }; + let width = self.obj().width(); + + if width <= 0 { + return; + } + + let clipped_size = self.label_widget.width() - width; + + if clipped_size <= 0 { + self.obj().snapshot_child(&self.label_widget, snapshot); + return; + } + + let width = width as f32; + let child_snapshot = gtk::Snapshot::new(); + self.obj().snapshot_child(&self.label_widget, &child_snapshot); + + let node = child_snapshot.to_node(); + + if node.is_none() { + self.obj().snapshot_child(&self.label_widget, snapshot); + return; + } + + let node = node.unwrap(); + + let node_bounds = node.bounds(); + let bounds = graphene::Rect::new(0.0, node_bounds.y().floor(), width, f32::ceil(node_bounds.height() + 1.0)); + + snapshot.push_mask(gsk::MaskMode::InvertedAlpha); + + if align > 0.0 { + snapshot.append_linear_gradient( + &graphene::Rect::new(0.0, bounds.y(), self.fade_width.get(), bounds.height()), + &graphene::Point::new(0.0, 0.0), + &graphene::Point::new(self.fade_width.get(), 0.0), + &[ + gsk::ColorStop::new(0.0, gdk::RGBA::new(0.0, 0.0, 0.0, 1.0)), + gsk::ColorStop::new(1.0, gdk::RGBA::new(0.0, 0.0, 0.0, 0.0)), + ], + ); + } + + if align < 1.0 { + snapshot.append_linear_gradient( + &graphene::Rect::new(width - self.fade_width.get(), bounds.y(), self.fade_width.get(), bounds.height()), + &graphene::Point::new(width, 0.0), + &graphene::Point::new(width - self.fade_width.get(), 0.0), + &[ + gsk::ColorStop::new(0.0, gdk::RGBA::new(0.0, 0.0, 0.0, 1.0)), + gsk::ColorStop::new(1.0, gdk::RGBA::new(0.0, 0.0, 0.0, 0.0)), + ], + ); + } + + snapshot.pop(); + + snapshot.push_clip(&bounds); + snapshot.append_node(&node); + snapshot.pop(); + + snapshot.pop(); + } +} + +impl FadingLabel { + fn set_label(&self, label: &str) { + if label == &self.get_label() { + return; + } + + self.label_widget.set_label(label); + self.obj().notify_label(); + } + + fn get_label(&self) -> String { + self.label_widget.label().into() + } + + fn set_align(&self, align: f32) { + let align = num::clamp(align, 0.0, 1.0); + + if abs_diff_eq!(self.align.get(), align) { + return; + } + + self.align.set(align); + + self.obj().queue_allocate(); + self.obj().notify_align(); + } + + fn set_fade_width(&self, fade_width: f32) { + if abs_diff_eq!(self.fade_width.get(), fade_width) { + return; + } + + self.fade_width.set(fade_width); + + self.obj().queue_allocate(); + self.obj().notify_align(); + } + + fn is_rtl(&self) -> bool { + let direction = pango::find_base_dir(&self.get_label()); + + match direction { + pango::Direction::Rtl => true, + pango::Direction::Ltr => false, + _ => self.obj().direction() == gtk::TextDirection::Rtl, + } + } +} diff --git a/src/twl/mod.rs b/src/twl/mod.rs index 57a88d5..1ba8f02 100644 --- a/src/twl/mod.rs +++ b/src/twl/mod.rs @@ -1,6 +1,5 @@ mod zoom_controls; mod zoom_controls_imp; -use glib::subclass::SignalInvocationHint; pub use zoom_controls::*; mod style_switcher; @@ -11,8 +10,9 @@ mod panel; mod panel_imp; pub use panel::*; -mod tile_header; -pub use tile_header::*; +mod panel_header; +mod panel_header_imp; +pub use panel_header::*; mod split_paned; @@ -20,9 +20,16 @@ mod panel_grid; mod panel_grid_imp; pub use panel_grid::*; -pub fn signal_accumulator_propagation(_hint: &SignalInvocationHint, return_accu: &mut glib::Value, handler_return: &glib::Value) -> bool { - let signal_propagate = glib::Propagation::from(handler_return.get::().unwrap_or(true)); +mod bin; +mod bin_imp; +pub use bin::*; - *return_accu = handler_return.clone(); - signal_propagate.into() -} +mod pack_box; +mod pack_box_imp; +pub use pack_box::*; + +mod fading_label; +mod fading_label_imp; +pub use fading_label::*; + +pub mod utils; diff --git a/src/twl/pack_box.rs b/src/twl/pack_box.rs new file mode 100644 index 0000000..669051f --- /dev/null +++ b/src/twl/pack_box.rs @@ -0,0 +1,28 @@ +use glib::{prelude::*, subclass::prelude::*}; + +use super::{pack_box_imp as imp, utils::TwlWidgetExt}; + +glib::wrapper! { + pub struct PackBox(ObjectSubclass) + @extends gtk::Widget, + @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; +} + +impl PackBox { + pub fn new() -> Self { + glib::Object::builder().build() + } + + pub fn append(&self, child: &impl IsA) { + self.imp().append(child); + } + + pub fn pack_start(&self, child: &impl IsA) { + self.imp().pack_start(child); + } + + pub fn pack_end(&self, child: &impl IsA) { + self.imp().pack_end(child); + } +} +impl TwlWidgetExt for PackBox {} diff --git a/src/twl/pack_box_imp.rs b/src/twl/pack_box_imp.rs new file mode 100644 index 0000000..91f491a --- /dev/null +++ b/src/twl/pack_box_imp.rs @@ -0,0 +1,137 @@ +use std::{cmp::Ordering, marker::PhantomData}; + +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib::{self, Properties}; +use gtk::graphene; + +use approx::abs_diff_eq; +use num_traits as num; + +use super::utils::{twl_widget_compute_expand, Orthogonal, TwlWidgetExt}; + +#[derive(Debug, Default, Properties)] +#[properties(wrapper_type=super::PackBox)] +pub struct PackBox { + container: gtk::Box, + start: gtk::Box, + center: gtk::Box, + end: gtk::Box, + + #[property(get=Self::get_orientation, set=Self::set_orientation, construct, explicit_notify, builder(gtk::Orientation::Horizontal))] + orientation: PhantomData, +} + +// impl Default for PackBox { +// fn default() -> Self { +// let orientation = gtk::Orientation::Horizontal; +// let spacing = 6; +// Self { +// container: gtk::Box::new(orientation, spacing), +// start: gtk::Box::new(orientation, spacing), +// center: gtk::Box::new(orientation, spacing), +// end: gtk::Box::new(orientation, spacing), + +// orientation: Default::default(), +// } +// } +// } + +#[glib::object_subclass] +impl ObjectSubclass for PackBox { + const NAME: &'static str = "TwlPackBox"; + type Type = super::PackBox; + type ParentType = gtk::Widget; + type Interfaces = (gtk::Buildable, gtk::Orientable); + + fn class_init(klass: &mut Self::Class) { + klass.set_layout_manager_type::(); + klass.set_css_name("pack_box"); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PackBox { + fn constructed(&self) { + self.parent_constructed(); + + self.container.set_parent(self.obj().as_ref()); + self.container.set_hexpand(true); + self.container.set_vexpand(true); + + self.container.append(&self.start); + self.container.append(&self.center); + self.container.append(&self.end); + + self.center.set_hexpand(true); + self.center.set_vexpand(true); + self.apply_orientation(); + } + + fn dispose(&self) { + self.container.unparent(); + } +} + +impl WidgetImpl for PackBox { + fn compute_expand(&self, hexpand: &mut bool, vexpand: &mut bool) { + twl_widget_compute_expand(&self.container, hexpand, vexpand); + } +} + +impl OrientableImpl for PackBox {} + +impl BuildableImpl for PackBox { + fn add_child(&self, builder: >k::Builder, child: &glib::Object, type_: Option<&str>) { + match (child.downcast_ref::(), type_) { + (Some(widget), Some(wtype)) if wtype == "start" => self.pack_start(widget), + (Some(widget), Some(wtype)) if wtype == "end" => self.pack_end(widget), + (Some(widget), _) => self.append(widget), + (_, _) => self.parent_add_child(builder, child, type_), + } + } +} + +impl PackBox { + fn set_orientation(&self, orientation: gtk::Orientation) { + if self.get_orientation() == orientation { + return; + } + + self.container.set_orientation(orientation); + + self.apply_orientation(); + self.obj().notify_orientation(); + } + + fn get_orientation(&self) -> gtk::Orientation { + self.container.orientation() + } + + fn apply_orientation(&self) { + let orientation = self.get_orientation(); + let is_horizontal = orientation == gtk::Orientation::Horizontal; + + self.start.set_orientation(orientation); + self.start.set_hexpand(!is_horizontal); + self.start.set_vexpand(is_horizontal); + + self.center.set_orientation(orientation); + + self.end.set_orientation(orientation); + self.end.set_hexpand(!is_horizontal); + self.end.set_vexpand(is_horizontal); + } + + pub fn pack_start(&self, widget: &impl IsA) { + self.start.append(widget); + } + + pub fn append(&self, widget: &impl IsA) { + self.center.append(widget); + } + + pub fn pack_end(&self, widget: &impl IsA) { + self.end.append(widget); + } +} diff --git a/src/twl/panel.rs b/src/twl/panel.rs index ac752fb..b4b6157 100644 --- a/src/twl/panel.rs +++ b/src/twl/panel.rs @@ -1,6 +1,6 @@ -use glib::{prelude::*, subclass::prelude::*}; +use glib::{closure_local, prelude::*, subclass::prelude::*}; -use super::panel_imp as imp; +use super::{panel_imp as imp, utils::TwlWidgetExt}; glib::wrapper! { pub struct Panel(ObjectSubclass) @@ -9,8 +9,8 @@ glib::wrapper! { } impl Panel { - pub fn new(child: &impl IsA) -> Self { - glib::Object::builder().property("child", child).build() + pub fn new(content: &impl IsA) -> Self { + glib::Object::builder().property("content", content).build() } pub fn set_closing(&self, closing: bool) { @@ -20,4 +20,10 @@ impl Panel { pub fn closing(&self) -> bool { self.imp().closing.get() } + + pub fn connect_close(&self, f: F) -> glib::SignalHandlerId { + self.connect_closure("close", false, closure_local!(move |obj: Self| { f(&obj) })) + } } + +impl TwlWidgetExt for Panel {} diff --git a/src/twl/panel_grid.rs b/src/twl/panel_grid.rs index 3646392..6f7e707 100644 --- a/src/twl/panel_grid.rs +++ b/src/twl/panel_grid.rs @@ -1,7 +1,7 @@ use glib::{closure_local, prelude::*}; use gtk::subclass::prelude::*; -use super::{panel_grid_imp as imp, Panel}; +use super::{panel_grid_imp as imp, utils::TwlWidgetExt, Panel}; glib::wrapper! { pub struct PanelGrid(ObjectSubclass) @@ -50,3 +50,5 @@ impl PanelGrid { ) } } + +impl TwlWidgetExt for PanelGrid {} diff --git a/src/twl/panel_grid_imp.rs b/src/twl/panel_grid_imp.rs index 0055099..58116c6 100644 --- a/src/twl/panel_grid_imp.rs +++ b/src/twl/panel_grid_imp.rs @@ -8,9 +8,10 @@ use glib::{clone, subclass::Signal, Properties}; use once_cell::sync::Lazy; use tracing::*; -use crate::twl::signal_accumulator_propagation; +use crate::twl::utils::signal_accumulator_propagation; use super::split_paned::SplitPaned; +use super::utils::TwlWidgetExt; use super::Panel; #[derive(Debug, Default, Properties)] @@ -39,6 +40,7 @@ impl ObjectSubclass for PanelGrid { fn class_init(klass: &mut Self::Class) { klass.set_layout_manager_type::(); + klass.set_css_name("panel_grid"); } } @@ -124,29 +126,26 @@ impl PanelGrid { where T: IsA + ObjectType, { - self.get_all_inner(&self.inner).into_iter().collect() + self.get_all_inner(&self.inner.upcast_ref()).into_iter().collect() } - fn get_all_inner(&self, root: &impl IsA) -> HashSet + fn get_all_inner(&self, root: >k::Widget) -> HashSet where T: IsA + ObjectType, { let mut elems = HashSet::new(); - if let Ok(relem) = root.as_ref().clone().downcast() { + if let Ok(relem) = root.clone().downcast() { elems.insert(relem); } - let mut sibling = root.first_child(); - while let Some(widget) = sibling { - if let Ok(elem) = widget.clone().downcast() { + for child in root.iter_children() { + if let Ok(elem) = child.clone().downcast() { elems.insert(elem); } - let child_elems = self.get_all_inner(&widget); + let child_elems = self.get_all_inner(&child); elems.extend(child_elems); - - sibling = widget.next_sibling(); } elems @@ -161,6 +160,11 @@ impl PanelGrid { this.on_panel_focus(&panel); } })); + + panel.connect_close(clone!(@weak self as this => move |p| { + this.close_panel(p); + })); + panel } @@ -175,14 +179,16 @@ impl PanelGrid { } pub fn split(&self, child: &impl IsA, orientation: Option) -> Panel { - let root_panel = self.selected_panel.borrow().clone().or_else(|| self.get_all::().first().cloned()); - debug!("root panel {:?}", root_panel); + let active_panel = self.selected_panel.borrow().clone().or_else(|| self.get_all::().first().cloned()); + debug!("active panel {:?}", active_panel); - if let Some(root_panel) = root_panel { + if let Some(active_panel) = active_panel { + debug!("split: split active panel"); let panel = self.create_panel(child); - self.split_panel(&root_panel, &panel, orientation); + self.split_panel(&active_panel, &panel, orientation); panel } else { + debug!("split: set initial child"); self.set_initial_child(child) } } @@ -190,6 +196,7 @@ impl PanelGrid { fn split_panel(&self, panel: &Panel, new_panel: &Panel, orientation: Option) { let new_paned = gtk::Paned::new(orientation.unwrap_or_else(|| self.preferred_orientation(panel))); new_paned.set_wide_handle(self.wide_handle.get()); + debug!("panel {:?} parent {:?}", panel, panel.parent()); match panel.parent().and_downcast::() { // if the widget does not belong to a paned, it has to be the root diff --git a/src/twl/panel_header.rs b/src/twl/panel_header.rs new file mode 100644 index 0000000..b8d7033 --- /dev/null +++ b/src/twl/panel_header.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use super::{panel_header_imp as imp, utils::TwlWidgetExt, Panel}; +use glib::closure_local; +use gtk::prelude::*; + +glib::wrapper! { + pub struct PanelHeader(ObjectSubclass) + @extends gtk::Widget, + @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable; +} + +impl PanelHeader { + pub fn new(panel: &impl IsA) -> Self { + glib::Object::builder().property("panel", panel).build() + } +} + +impl TwlWidgetExt for PanelHeader {} diff --git a/src/twl/panel_header.ui b/src/twl/panel_header.ui new file mode 100644 index 0000000..74d8e92 --- /dev/null +++ b/src/twl/panel_header.ui @@ -0,0 +1,116 @@ + + + + + + + diff --git a/src/twl/panel_header_imp.rs b/src/twl/panel_header_imp.rs new file mode 100644 index 0000000..585bbd1 --- /dev/null +++ b/src/twl/panel_header_imp.rs @@ -0,0 +1,140 @@ +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib::subclass::Signal; +use glib::{self, clone, Properties, WeakRef}; +use gtk::{template_callbacks, CompositeTemplate}; +use itertools::Itertools; +use once_cell::sync::Lazy; +use std::cell::{Cell, RefCell}; +use std::marker::PhantomData; + +use super::utils::TwlWidgetExt; +use super::{Bin, FadingLabel, PackBox, Panel}; + +// #[derive(Debug, Default)] +// pub struct Header {} + +// #[glib::object_interface] +// unsafe impl ObjectInterface for Header { +// const NAME: &'static str = "TwlHeader"; +// } + +#[derive(Debug, CompositeTemplate, Properties)] +#[template(resource = "/io/github/vhdirk/Twl/gtk/panel_header.ui")] +#[properties(wrapper_type=super::PanelHeader)] +pub struct PanelHeader { + #[property(get, set=Self::set_title, construct, nullable)] + title: RefCell>, + + #[property(get=Self::get_title_widget, set=Self::set_title_widget, construct, nullable)] + title_widget: PhantomData>, + + #[template_child] + container: TemplateChild, + + #[template_child] + title_container: TemplateChild, + + #[property(construct_only)] + panel: WeakRef, +} + +impl Default for PanelHeader { + fn default() -> Self { + Self { + title: Default::default(), + title_widget: Default::default(), + + container: Default::default(), + title_container: Default::default(), + panel: Default::default(), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for PanelHeader { + const NAME: &'static str = "TwlPanelHeader"; + type Type = super::PanelHeader; + type ParentType = gtk::Widget; + + fn class_init(klass: &mut Self::Class) { + klass.set_layout_manager_type::(); + klass.set_css_name("panel_header"); + klass.bind_template(); + klass.bind_template_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PanelHeader { + fn constructed(&self) { + self.parent_constructed(); + self.setup(); + } + + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| vec![Signal::builder("close").build()]); + SIGNALS.as_ref() + } + + fn dispose(&self) { + self.container.unparent() + } +} + +impl WidgetImpl for PanelHeader { + fn request_mode(&self) -> gtk::SizeRequestMode { + self.container.request_mode() + } +} + +#[gtk::template_callbacks] +impl PanelHeader { + fn setup(&self) {} + + fn construct_title_label(&self, title: Option<&str>) -> FadingLabel { + let label = FadingLabel::new(title); + label.add_css_class("title"); + label.set_valign(gtk::Align::Center); + label + } + + fn get_title_widget(&self) -> Option { + self.title_container.child() + } + + fn set_title_widget(&self, widget: Option<>k::Widget>) { + if self.title_container.child().as_ref() == widget { + return; + } + + self.title_container.set_child(widget); + + self.obj().notify_title_widget(); + self.obj().notify_title(); + } + + fn set_title(&self, title: Option<&str>) { + *self.title.borrow_mut() = title.map(ToString::to_string); + match title { + Some(title) => { + self.set_title_widget(Some(self.construct_title_label(Some(title)).upcast_ref())); + }, + None => { + self.set_title_widget(None::<>k::Widget>); + }, + } + } + + #[template_callback] + fn on_close_clicked(&self) { + if let Some(panel) = self.panel.upgrade() { + panel.emit_by_name::<()>("close", &[]); + } + } +} diff --git a/src/twl/panel_imp.rs b/src/twl/panel_imp.rs index ff4cf40..9980b0d 100644 --- a/src/twl/panel_imp.rs +++ b/src/twl/panel_imp.rs @@ -1,22 +1,21 @@ -use std::cell::{Cell, RefCell}; - +use adw::prelude::*; use adw::subclass::prelude::*; -use glib::prelude::*; +use glib::subclass::basic::ClassStruct; use glib::subclass::Signal; use glib::Properties; +use gtk::{graphene, gsk}; +use num_traits as num; use once_cell::sync::Lazy; -use tracing::warn; -use vte::BoxExt; -use vte::WidgetExt; +use std::cell::{Cell, OnceCell, RefCell}; +use std::cmp; +use std::marker::PhantomData; +use tracing::{info, warn}; + +use super::PanelHeader; -#[derive(Debug, Default, Properties)] +#[derive(Debug, Properties)] #[properties(wrapper_type=super::Panel)] pub struct Panel { - container: gtk::Box, - - #[property(get, set=Self::set_title, construct, nullable)] - title: RefCell>, - #[property(get, set, construct)] needs_attention: Cell, @@ -26,14 +25,11 @@ pub struct Panel { #[property(get, set, construct, nullable)] tooltip: RefCell>, - #[property(get, set=Self::set_child, construct, nullable)] - child: RefCell>, - - #[property(get, set=Self::set_title_widget, construct, nullable)] - title_widget: RefCell>, + #[property(get, set=Self::set_content, construct, nullable)] + content: RefCell>, - #[property(get, set=Self::set_show_header, construct)] - show_header: Cell, + #[property(get=Self::get_show_header, set=Self::set_show_header, construct)] + show_header: PhantomData, pub closing: Cell, // #[property(get, set, construct)] @@ -41,7 +37,16 @@ pub struct Panel { // #[property(get, set, construct)] // live_thumbnail: Cell, + #[property(get, explicit_notify)] + header_height: Cell, + + #[property(get, set, builder(adw::ToolbarStyle::Flat))] + header_style: Cell, + header_revealer: gtk::Revealer, + + #[property(get)] + header: OnceCell, // PAGE_PROP_PARENT, // PAGE_PROP_SELECTED, // PAGE_PROP_LOADING, @@ -54,6 +59,30 @@ pub struct Panel { // PAGE_PROP_LIVE_THUMBNAIL, } +impl Default for Panel { + fn default() -> Self { + Self { + needs_attention: Default::default(), + + icon: Default::default(), + + tooltip: Default::default(), + + content: Default::default(), + + show_header: Default::default(), + + closing: Default::default(), + + header_height: Cell::new(-1), + header_style: Cell::new(adw::ToolbarStyle::Flat), + header_revealer: gtk::Revealer::new(), + + header: Default::default(), + } + } +} + #[glib::object_subclass] impl ObjectSubclass for Panel { const NAME: &'static str = "TwlPanel"; @@ -61,7 +90,8 @@ impl ObjectSubclass for Panel { type ParentType = gtk::Widget; fn class_init(klass: &mut Self::Class) { - klass.set_layout_manager_type::(); + // klass.set_layout_manager_type::(); + klass.set_css_name("panel"); } } @@ -69,88 +99,148 @@ impl ObjectSubclass for Panel { impl ObjectImpl for Panel { fn constructed(&self) { self.parent_constructed(); - self.setup(); } fn dispose(&self) { - self.container.unparent(); - if let Some(title_widget) = self.title_widget.borrow().as_ref() { - title_widget.unparent(); + if let Some(content) = self.content.borrow().as_ref() { + content.unparent(); + } + + self.header_revealer.set_child(None::<>k::Widget>); + self.header_revealer.unparent(); + } + + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| vec![Signal::builder("close").build()]); + SIGNALS.as_ref() + } +} +impl WidgetImpl for Panel { + fn request_mode(&self) -> gtk::SizeRequestMode { + match self.content.borrow().as_ref() { + Some(content) => content.request_mode(), + None => gtk::SizeRequestMode::ConstantSize, + } + } + + fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) { + let (header_min, header_nat, _, _) = self.header_revealer.measure(orientation, for_size); + + let (content_min, content_nat) = match self.content.borrow().as_ref() { + Some(content) => { + let (content_min, content_nat, _, _) = content.measure(orientation, for_size); + (content_min, content_nat) + }, + None => (0, 0), + }; + + let (minimum, natural) = match orientation { + gtk::Orientation::Horizontal => (cmp::max(content_min, header_min), cmp::max(content_nat, header_nat)), + _ => (content_min + header_min, content_nat + header_nat), + }; + + (minimum, natural, -1, -1) + } + + fn size_allocate(&self, width: i32, height: i32, _baseline: i32) { + let (header_min, header_nat, _, _) = self.header_revealer.measure(gtk::Orientation::Vertical, -1); + + let content_min = self + .content + .borrow() + .as_ref() + .map(|content| { + let (content_min, _, _, _) = content.measure(gtk::Orientation::Vertical, -1); + cmp::max(0, content_min) + }) + .unwrap_or(0); + + let header_height = num::clamp(height - content_min, header_min, header_nat); + let content_height = height - header_height; + let content_offset = header_height; + + if self.header_height.get() != header_height { + self.header_height.set(header_height); + self.obj().notify_header_height(); + } + + self.header_revealer.allocate(width, header_height, -1, None); + if let Some(content) = self.content.borrow().as_ref() { + content.allocate( + width, + content_height, + -1, + Some(gsk::Transform::new().translate(&graphene::Point::new(0.0, content_offset as f32))), + ) } } } -impl WidgetImpl for Panel {} impl BuildableImpl for Panel { fn add_child(&self, builder: >k::Builder, child: &glib::Object, type_: Option<&str>) { match (child.downcast_ref::(), type_) { - (Some(widget), Some(wtype)) if wtype == "title" => self.set_title_widget(Some(widget)), - (Some(widget), _) => self.set_child(Some(widget)), - (None, _) => self.parent_add_child(builder, child, type_), + // (Some(widget), Some(wtype)) if wtype == "header" => match widget.clone().downcast::() { + // Err(err) => warn!("unable to use widget {:?} as header {:?}", widget, err), + // Ok(header) => self.set_header(Some(&header)), + // }, + // (Some(widget), _) if wtype == "content" => self.set_content(Some(widget)), + (Some(widget), _) => self.set_content(Some(widget)), + (_, _) => self.parent_add_child(builder, child, type_), } } } impl Panel { fn setup(&self) { - self.container.set_parent(&*self.obj()); - self.obj().set_focusable(true); - self.obj().set_focus_child(Some(&self.container)); - } + let header = PanelHeader::new(self.obj().as_ref()); + self.header.set(header.clone()).expect("Header should not have been set yet"); + self.obj().set_overflow(gtk::Overflow::Hidden); - fn set_child(&self, child: Option<>k::Widget>) { - self.remove_child(); + // self->header_style = ADW_TOOLBAR_FLAT; - if let Some(child) = child { - self.container.append(child); - *self.child.borrow_mut() = Some(child.clone()); - } + self.header_revealer.set_overflow(gtk::Overflow::Visible); + self.header_revealer.set_vexpand(true); + + self.header_revealer.set_parent(&*self.obj()); + + self.header_revealer.set_reveal_child(true); + self.header_revealer.set_child(Some(&header)); - self.container.set_focus_child(child); + self.setup_content(); } - fn set_title_widget(&self, child: Option<>k::Widget>) { - if self.title_widget.borrow().as_ref() == child { + fn set_content(&self, content: Option<>k::Widget>) { + info!("panel: set content {:?}", content); + + // TODO: not yet initialized? weird + if self.header_revealer.parent().is_none() { + *self.content.borrow_mut() = content.cloned(); return; } - if let Some(widget) = self.title_widget.borrow().as_ref() { - widget.unparent(); + if content == self.content.borrow().as_ref() { + return; } - *self.title_widget.borrow_mut() = child.cloned(); - - if let Some(widget) = child { - widget.set_parent(&*self.obj()); + if let Some(previous) = self.content.borrow().as_ref() { + previous.unparent(); } - let _ = self.obj().freeze_notify(); - - self.obj().notify_title_widget(); - self.obj().notify_title(); - } - - fn set_title(&self, title: Option<&str>) { - // match title { - // Some(title) => { - // self.set_title_widget(Some(gtk::Label::new(Some(title)).upcast_ref())); - // }, - // None => { - // self.set_title_widget(None::<>k::Widget>); - // }, - // } + *self.content.borrow_mut() = content.cloned(); + self.setup_content(); } - fn remove_child(&self) { - if let Some(child) = self.child.borrow().as_ref() { - child.unparent(); + fn setup_content(&self) { + if let Some(content) = self.content.borrow().as_ref() { + content.insert_before(&*self.obj(), Some(&self.header_revealer)); } - *self.child.borrow_mut() = None; } - fn set_show_header(&self, show_header: bool) { - self.show_header.set(show_header); - // TODO + self.header_revealer.set_reveal_child(show_header) + } + + fn get_show_header(&self) -> bool { + self.header_revealer.is_child_revealed() } } diff --git a/src/twl/style_switcher.ui b/src/twl/style_switcher.ui index f2c8792..c3aa516 100644 --- a/src/twl/style_switcher.ui +++ b/src/twl/style_switcher.ui @@ -7,13 +7,13 @@ BlackBox is licensed GNU GPLv3 --> - + +