diff --git a/cosmic-panel-bin/src/space/layout.rs b/cosmic-panel-bin/src/space/layout.rs index a5a9fadd..722db82b 100644 --- a/cosmic-panel-bin/src/space/layout.rs +++ b/cosmic-panel-bin/src/space/layout.rs @@ -161,8 +161,7 @@ impl PanelSpace { }) .collect_vec(); - let is_dock = !self.config.expand_to_edges() - || self.animate_state.as_ref().is_some_and(|a| !(a.cur.expanded > 0.5)); + let is_dock_layout = self.config.name.eq_ignore_ascii_case("Dock"); let mut windows_left = to_map .iter() .cloned() @@ -296,7 +295,7 @@ impl PanelSpace { .collect_vec(); make_indices_contiguous(&mut windows_right); - if is_dock { + if is_dock_layout { windows_center = windows_left .drain(..) .chain(windows_center) @@ -330,7 +329,7 @@ impl PanelSpace { self.space.refresh(); let padding_overlap = self.config.padding_overlap(); let applet_padding = - self.config.size.get_applet_shrinkable_padding(true) as f32 * padding_overlap; + self.config.get_applet_shrinkable_padding(true) as f32 * padding_overlap; let mut bg_color = self.bg_color(); for c in 0..3 { @@ -348,8 +347,9 @@ impl PanelSpace { PanelAnchor::Left | PanelAnchor::Right => (self.dimensions.w, self.dimensions.h), PanelAnchor::Top | PanelAnchor::Bottom => (self.dimensions.h, self.dimensions.w), }; - let is_dock = !self.config.expand_to_edges(); - if is_dock { + let is_compact = !self.config.expand_to_edges_effective(); + let is_dock_layout = self.config.name.eq_ignore_ascii_case("Dock"); + if is_dock_layout { if let Some(left_button) = left_overflow_button.take() { self.space.unmap_elem(&CosmicMappedInternal::OverflowButton(left_button)); } @@ -547,29 +547,52 @@ impl PanelSpace { let center_sum = center_sum_scaled / self.scale; let right_sum = right_sum_scaled / self.scale; + let output_length = new_list_dim_length.max(1); + let dock_length = if is_compact || self.animate_state.is_some() { + self.config + .dock_length_percent + .and_then(|p| (p > 0 && p <= 100).then_some(p)) + .map(|p| ((output_length as f32) * (p as f32 / 100.0)).round() as i32) + .unwrap_or(new_logical_length) + .clamp(1, output_length) + } else { + output_length + }; let container_length = if let Some(anim_state) = self.animate_state.as_ref() { - (new_logical_length as f32 - + (new_list_dim_length - new_logical_length) as f32 * anim_state.cur.expanded) + (dock_length as f32 + (output_length - dock_length) as f32 * anim_state.cur.expanded) as i32 - } else if is_dock { - new_logical_length + } else if is_compact { + dock_length } else { - new_list_dim_length + output_length }; self.container_length = container_length; - let container_lengthwise_pos = (new_list_dim_length - container_length) / 2; + let dock_position = self + .config + .dock_position_percent + .map(|p| p.clamp(0, 100)) + .unwrap_or(50) as f32 + / 100.0; + let container_lengthwise_pos = if is_compact { + let available = (output_length - container_length).max(0); + (available as f32 * dock_position).round() as i32 + } else { + 0 + }; - let mut center_pos = layer_major as f64 / 2. - center_sum / 2.; + let layout_major = if is_compact { container_length } else { layer_major }; + let layout_start = if is_compact { container_lengthwise_pos as f64 } else { 0.0 }; - let left_pos = container_lengthwise_pos as f64 + padding_u32 as f64; - let mut right_pos = new_list_dim_length as f64 - - container_lengthwise_pos as f64 - - right_sum - - padding_u32 as f64; + let mut center_pos = layout_start + layout_major as f64 / 2. - center_sum / 2.; - let one_third = (layer_major as f64 - (spacing_u32 * num_lists.saturating_sub(1)) as f64) + let left_pos = layout_start + padding_u32 as f64; + let mut right_pos = + layout_start + layout_major as f64 - right_sum - padding_u32 as f64; + + let one_third = (layout_major as f64 + - (spacing_u32 * num_lists.saturating_sub(1)) as f64) / (3.min(num_lists) as f64); - let one_half = layer_major as f64 / (2.min(num_lists) as f64); + let one_half = layout_major as f64 / (2.min(num_lists) as f64); let larger_side = left_sum.max(right_sum); let larger_side = if left_overflow_button.is_some() || right_overflow_button.is_some() { larger_side.max(container_length as f64 / 3.) @@ -578,14 +601,14 @@ impl PanelSpace { }; let mut target_center_len = - (layer_major as f64 - larger_side * (2.)).max(one_third).min(layer_major as f64); + (layout_major as f64 - larger_side * (2.)).max(one_third).min(layout_major as f64); if num_lists == 1 { target_center_len -= padding_u32 as f64 * 2.; } else { target_center_len -= spacing_u32 as f64; } let target_left_len = if !has_center { - (layer_major as f64 + (layout_major as f64 - right_sum.min(one_half) - (spacing_u32 as f64) / 2. - padding_u32 as f64) @@ -597,10 +620,10 @@ impl PanelSpace { - padding_u32 as f64) .max(one_third) } - .min(layer_major as f64); + .min(layout_major as f64); let target_right_len = if !has_center { - (layer_major as f64 + (layout_major as f64 - left_sum.min(one_half) - (spacing_u32 as f64) / 2. - padding_u32 as f64) @@ -612,9 +635,9 @@ impl PanelSpace { - padding_u32 as f64) .max(one_third) } - .min(layer_major as f64); - let suggested_size = ((self.config.size.get_applet_icon_size(true) as f64 - + self.config.size.get_applet_padding(true) as f64 * 2.) + .min(layout_major as f64); + let suggested_size = ((self.config.get_applet_icon_size(true) as f64 + + self.config.get_applet_padding(true) as f64 * 2.) * -1.5 // allows some wiggle room * self.scale) as i32; let center_overflow = (center_sum - target_center_len) as i32; @@ -626,7 +649,7 @@ impl PanelSpace { bail!("overflow: {}", overflow) } - if !is_dock && self.animate_state.is_none() { + if !is_dock_layout && self.animate_state.is_none() { let left_overflow = (left_sum - target_left_len) as i32; if left_overflow < suggested_size { @@ -821,7 +844,7 @@ impl PanelSpace { _ = self.shared.panel_tx.send(crate::PanelCalloopMsg::MinimizeRect { output, applet_info: MinimizeApplet { - priority: if is_dock { 1 } else { 0 }, + priority: if is_dock_layout { 1 } else { 0 }, rect: new_rect, surface: layer.wl_surface().clone(), }, @@ -880,7 +903,7 @@ impl PanelSpace { } else { panel_size.h = container_length_scaled; } - let (mut w, mut h) = if is_dock { + let (mut w, mut h) = if is_compact { if self.config.is_horizontal() { (container_length, new_dim.h) } else { @@ -989,35 +1012,29 @@ impl PanelSpace { // disable input regions for hidden stacked panels if !matches!(self.visibility, Visibility::Hidden) || self.additional_gap == 0 { - if is_dock { - let (layer_length, actual_length) = if self.config.is_horizontal() { - (new_dim.w, self.actual_size.w) - } else { - (new_dim.h, self.actual_size.h) - }; - let side = (layer_length as u32 - actual_length as u32) / 2; - + if is_compact { + let side = container_lengthwise_pos; let (mut loc, mut size) = match self.config.anchor { PanelAnchor::Left => ( - (-1, side as i32), + (-1, side), ( new_logical_crosswise_dim + self.gap() as i32 + 1 + anim_gap, container_length, ), ), PanelAnchor::Right => ( - (-anim_gap, side as i32), + (-anim_gap, side), (new_logical_crosswise_dim + self.gap() as i32 + 1, container_length), ), PanelAnchor::Top => ( - (side as i32, -1), + (side, -1), ( container_length, new_logical_crosswise_dim + self.gap() as i32 + 1 + anim_gap, ), ), PanelAnchor::Bottom => ( - (side as i32, 0 - anim_gap), + (side, 0 - anim_gap), (container_length, new_logical_crosswise_dim + self.gap() as i32 + 1), ), }; @@ -1108,7 +1125,7 @@ impl PanelSpace { let mut overflow_cnt: usize = 0; let cur_cnt = elements.len(); - let applet_size_unit = self.config.size.get_applet_icon_size_with_padding(true); + let applet_size_unit = self.config.get_applet_icon_size_with_padding(true); let padding = self.config.padding as i32; let spacing = self.config.spacing as i32; let Some(output) = self.output.as_ref().map(|o| o.1.clone()) else { @@ -1143,13 +1160,13 @@ impl PanelSpace { }); let (major_padding, cross_padding) = ( - self.config.size.get_applet_shrinkable_padding(true), - self.config.size.get_applet_padding(true), + self.config.get_applet_shrinkable_padding(true), + self.config.get_applet_padding(true), ); let (applet_size_unit_major, applet_size_unit_cross) = ( - self.config.size.get_applet_icon_size(true) + 2 * major_padding as u32, - self.config.size.get_applet_icon_size(true) + 2 * cross_padding as u32, + self.config.get_applet_icon_size(true) + 2 * major_padding as u32, + self.config.get_applet_icon_size(true) + 2 * cross_padding as u32, ); for e in elements { @@ -1212,7 +1229,7 @@ impl PanelSpace { ) -> OverflowClientPartition { let mut overflow_partition = OverflowClientPartition::default(); overflow_partition.suggested_size = - (self.config.size.get_applet_icon_size_with_padding(true) as f64 + (self.config.get_applet_icon_size_with_padding(true) as f64 + 2. * self.config.get_applet_padding(true) as f64) .round() as u32; for c in clients { @@ -1273,7 +1290,7 @@ impl PanelSpace { let g = self.clients_center.lock().unwrap(); let left_g = self.clients_left.lock().unwrap(); let right_g = self.clients_right.lock().unwrap(); - let center: Vec<&PanelClient> = if self.config.expand_to_edges { + let center: Vec<&PanelClient> = if self.config.expand_to_edges_effective() { g.iter().collect() } else { left_g.iter().chain(g.iter()).chain(right_g.iter()).collect() @@ -1300,7 +1317,7 @@ impl PanelSpace { force_smaller: bool, ) -> u32 { info!("Overflow: {overflow} in section {section:?}"); - let unit_size = self.config.size.get_applet_icon_size_with_padding(true); + let unit_size = self.config.get_applet_icon_size_with_padding(true); let mut sum = 0.; for ShrinkableClient { window: w, priority, shrink_size: min_units, .. } in @@ -1405,8 +1422,8 @@ impl PanelSpace { return overflow; } let (major_padding, cross_padding) = ( - self.config.size.get_applet_shrinkable_padding(true), - self.config.size.get_applet_padding(true), + self.config.get_applet_shrinkable_padding(true), + self.config.get_applet_padding(true), ); let (h_padding, v_padding) = if self.config.is_horizontal() { (major_padding as f32, cross_padding as f32) @@ -1421,8 +1438,8 @@ impl PanelSpace { }; let mut overflow_cnt = overflow_space.elements().count(); let (applet_size_unit_major, applet_size_unit_cross) = ( - self.config.size.get_applet_icon_size(true) + 2 * major_padding as u32, - self.config.size.get_applet_icon_size(true) + 2 * cross_padding as u32, + self.config.get_applet_icon_size(true) + 2 * major_padding as u32, + self.config.get_applet_icon_size(true) + 2 * cross_padding as u32, ); let (applet_size_unit_h, applet_size_unit_v) = if is_horizontal { (applet_size_unit_major, applet_size_unit_cross) @@ -1555,7 +1572,7 @@ impl PanelSpace { if self.space.elements().all(|e| !matches!(e, CosmicMappedInternal::OverflowButton(e) if e.with_program(|p| p.id == id))) { let overflow_button_loc = (0, 0); - let icon_size = self.config.size.get_applet_icon_size(true); + let icon_size = self.config.get_applet_icon_size(true); let icon = if self.config.is_horizontal() { "view-more-horizontal-symbolic" } else { @@ -1632,8 +1649,8 @@ impl PanelSpace { let left = self.clients_left.lock().unwrap(); let mut clients = self.shrinkable_clients(left.iter()); drop(left); - let suggested_size = self.config.size.get_applet_icon_size(true) - + self.config.size.get_applet_padding(true) as u32 * 2; + let suggested_size = self.config.get_applet_icon_size(true) + + self.config.get_applet_padding(true) as u32 * 2; if clients.shrinkable_is_relaxed(self.config.is_horizontal(), self.scale) { Self::move_from_overflow( extra_space, @@ -1662,8 +1679,8 @@ impl PanelSpace { let mut clients = self.shrinkable_clients(center.iter()); drop(center); if clients.shrinkable_is_relaxed(self.config.is_horizontal(), self.scale) { - let suggested_size = self.config.size.get_applet_icon_size(true) - + self.config.size.get_applet_padding(true) as u32 * 2; + let suggested_size = self.config.get_applet_icon_size(true) + + self.config.get_applet_padding(true) as u32 * 2; Self::move_from_overflow( extra_space, self.config.is_horizontal(), @@ -1707,8 +1724,8 @@ impl PanelSpace { _ => {}, }; } - let suggested_size = self.config.size.get_applet_icon_size(true) - + self.config.size.get_applet_padding(true) as u32 * 2; + let suggested_size = self.config.get_applet_icon_size(true) + + self.config.get_applet_padding(true) as u32 * 2; self.relax_overflow_left(u32::MAX, &mut left_overflow_button); self.relax_overflow_center(u32::MAX, &mut center_overflow_button); self.relax_overflow_right(u32::MAX, &mut right_overflow_button); @@ -1744,8 +1761,8 @@ impl PanelSpace { let mut clients = self.shrinkable_clients(right.iter()); if clients.shrinkable_is_relaxed(self.config.is_horizontal(), self.scale) { - let suggested_size = self.config.size.get_applet_icon_size(true) - + self.config.size.get_applet_padding(true) as u32 * 2; + let suggested_size = self.config.get_applet_icon_size(true) + + self.config.get_applet_padding(true) as u32 * 2; Self::move_from_overflow( extra_space, self.config.is_horizontal(), diff --git a/cosmic-panel-bin/src/space/panel_space.rs b/cosmic-panel-bin/src/space/panel_space.rs index 09a89b9b..8d63a71c 100644 --- a/cosmic-panel-bin/src/space/panel_space.rs +++ b/cosmic-panel-bin/src/space/panel_space.rs @@ -715,7 +715,7 @@ impl PanelSpace { if let Some(animatable_state) = self.animate_state.as_ref() { animatable_state.cur.border_radius } else { - self.config.border_radius + self.config.get_effective_border_radius() } } @@ -1532,8 +1532,8 @@ impl PanelSpace { } else { let start = AnimatableState { bg_color: self.colors.bg_color(self.config.opacity), - border_radius: self.config.border_radius, - expanded: if self.config.expand_to_edges { 1.0 } else { 0.0 }, + border_radius: self.config.get_effective_border_radius(), + expanded: if self.config.expand_to_edges_effective() { 1.0 } else { 0.0 }, gap: self.gap(), }; let cur = start.clone(); @@ -1752,7 +1752,7 @@ impl PanelSpace { } } - if self.config.expand_to_edges != config.expand_to_edges { + if self.config.expand_to_edges_effective() != config.expand_to_edges_effective() { self.reset_overflow(); } @@ -1765,14 +1765,14 @@ impl PanelSpace { if animate { let start = AnimatableState { bg_color: self.colors.bg_color(self.config.opacity), - border_radius: self.config.border_radius, - expanded: if self.config.expand_to_edges { 1.0 } else { 0.0 }, + border_radius: self.config.get_effective_border_radius(), + expanded: if self.config.expand_to_edges_effective() { 1.0 } else { 0.0 }, gap: self.gap(), }; let end = AnimatableState { bg_color, - border_radius: config.border_radius, - expanded: if config.expand_to_edges { 1.0 } else { 0.0 }, + border_radius: config.get_effective_border_radius(), + expanded: if config.expand_to_edges_effective() { 1.0 } else { 0.0 }, gap: config.get_effective_anchor_gap() as u16, }; if let Some(animated_state) = self.animate_state.as_mut() { diff --git a/cosmic-panel-config/src/container_config.rs b/cosmic-panel-config/src/container_config.rs index 80ebda4b..86b2eeab 100644 --- a/cosmic-panel-config/src/container_config.rs +++ b/cosmic-panel-config/src/container_config.rs @@ -168,6 +168,11 @@ impl Default for CosmicPanelContainerConfig { padding: 0, spacing: 0, border_radius: 0, + dock_icon_size: None, + dock_corner_radius: None, + dock_length_percent: None, + dock_position_percent: None, + dock_custom_length: false, exclusive_zone: true, autohide: None, margin: 0, @@ -199,6 +204,11 @@ impl Default for CosmicPanelContainerConfig { padding: 4, spacing: 0, border_radius: 12, + dock_icon_size: None, + dock_corner_radius: None, + dock_length_percent: None, + dock_position_percent: None, + dock_custom_length: false, exclusive_zone: false, autohide: Some(crate::AutoHide { wait_time: 500, diff --git a/cosmic-panel-config/src/panel_config.rs b/cosmic-panel-config/src/panel_config.rs index b0562158..48688db5 100644 --- a/cosmic-panel-config/src/panel_config.rs +++ b/cosmic-panel-config/src/panel_config.rs @@ -397,6 +397,17 @@ pub struct CosmicPanelConfig { /// space between panel plugins pub spacing: u32, pub border_radius: u32, + /// optional dock icon size override + pub dock_icon_size: Option, + /// optional dock corner radius override + pub dock_corner_radius: Option, + /// optional dock length override as percent of output (0 = auto) + pub dock_length_percent: Option, + /// optional dock position override as percent of available space (0-100) + pub dock_position_percent: Option, + /// enable custom dock/panel length even when extend_to_edges is set + #[serde(default)] + pub dock_custom_length: bool, // TODO autohide & exclusive zone should not be able to both be enabled at once /// exclusive zone pub exclusive_zone: bool, @@ -432,6 +443,11 @@ impl PartialEq for CosmicPanelConfig { && self.padding == other.padding && self.spacing == other.spacing && self.border_radius == other.border_radius + && self.dock_icon_size == other.dock_icon_size + && self.dock_corner_radius == other.dock_corner_radius + && self.dock_length_percent == other.dock_length_percent + && self.dock_position_percent == other.dock_position_percent + && self.dock_custom_length == other.dock_custom_length && self.exclusive_zone == other.exclusive_zone && self.autohide == other.autohide && self.margin == other.margin @@ -463,6 +479,11 @@ impl Default for CosmicPanelConfig { exclusive_zone: true, autohide: Some(AutoHide::default()), border_radius: 8, + dock_icon_size: None, + dock_corner_radius: None, + dock_length_percent: None, + dock_position_percent: None, + dock_custom_length: false, margin: 4, opacity: 0.8, autohover_delay_ms: Some(500), @@ -513,18 +534,36 @@ impl CosmicPanelConfig { /// get applet icon dimensions pub fn get_applet_icon_size(&self, is_symbolic: bool) -> u32 { - self.size.get_applet_icon_size(is_symbolic) + self.dock_icon_size + .map(u32::from) + .unwrap_or_else(|| self.size.get_applet_icon_size(is_symbolic)) } pub fn get_applet_padding(&self, is_symbolic: bool) -> u16 { self.size.get_applet_padding(is_symbolic) } + pub fn get_applet_shrinkable_padding(&self, is_symbolic: bool) -> u16 { + self.size.get_applet_shrinkable_padding(is_symbolic) + } + + pub fn get_applet_icon_size_with_padding(&self, is_symbolic: bool) -> u32 { + self.get_applet_icon_size(is_symbolic) + self.get_applet_padding(is_symbolic) as u32 * 2 + } + + pub fn get_effective_border_radius(&self) -> u32 { + self.dock_corner_radius.map(u32::from).unwrap_or(self.border_radius) + } + + pub fn expand_to_edges_effective(&self) -> bool { + self.expand_to_edges && !self.dock_custom_length + } + /// get the priority of the panel /// higher priority panels will be created first and given more space when /// competing for space pub fn get_priority(&self) -> u32 { - let mut priority = if self.expand_to_edges() { 10000 } else { 0 }; + let mut priority = if self.expand_to_edges_effective() { 10000 } else { 0 }; if self.autohide().is_none() { priority += 1000; } @@ -541,7 +580,7 @@ impl CosmicPanelConfig { } pub fn get_stack_priority(&self) -> u32 { - let mut priority = if self.expand_to_edges() { 10000 } else { 0 }; + let mut priority = if self.expand_to_edges_effective() { 10000 } else { 0 }; // XXX for stack priority, most significant value is autohide if self.autohide().is_none() { priority += 100000; @@ -693,6 +732,7 @@ impl CosmicPanelConfig { return; } self.expand_to_edges = true; + self.dock_custom_length = false; self.margin = 0; self.border_radius = 0; self.anchor_gap = false;