Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/backend/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ impl AppData {
// TODO
Cmd::MoveWorkspaceBefore(_, _)
| Cmd::MoveWorkspaceAfter(_, _)
| Cmd::SetWorkspacePinned(_, _) => {}
| Cmd::SetWorkspacePinned(_, _)
| Cmd::RenameWorkspace(_, _) => {}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,5 @@ pub enum Cmd {
MoveWorkspaceAfter(ExtWorkspaceHandleV1, ExtWorkspaceHandleV1),
ActivateWorkspace(ExtWorkspaceHandleV1),
SetWorkspacePinned(ExtWorkspaceHandleV1, bool),
RenameWorkspace(ExtWorkspaceHandleV1, String),
}
12 changes: 12 additions & 0 deletions src/backend/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ impl AppData {
workspace_manager.commit();
}
}
Cmd::RenameWorkspace(workspace_handle, name) => {
if let Ok(workspace_manager) = self.workspace_state.workspace_manager().get()
&& let Some(cosmic_workspace) = self
.workspace_state
.workspaces()
.find(|w| w.handle == workspace_handle)
.and_then(|w| w.cosmic_handle.as_ref())
{
cosmic_workspace.rename(name);
workspace_manager.commit();
}
}
}
}

Expand Down
44 changes: 43 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ enum Msg {
UpdateToplevelIcon(String, Option<PathBuf>),
OnScroll(wl_output::WlOutput, ScrollDelta),
TogglePinned(ExtWorkspaceHandleV1),
StartRename(ExtWorkspaceHandleV1),
RenameInputChanged(String),
SubmitRename,
CancelRename,
EnteredWorkspaceSidebarEntry(ExtWorkspaceHandleV1, bool),
DbusInterface(zbus::Result<dbus::Interface>),
DBus(dbus::Event),
Expand Down Expand Up @@ -212,6 +216,7 @@ struct App {
rectangle_tracker: Option<RectangleTracker<RectId>>,
rects: HashMap<RectId, Rectangle>,
sub_ctr: u128,
renaming: Option<(ExtWorkspaceHandleV1, String)>,
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -627,7 +632,21 @@ impl Application for App {
self.wayland_cmd_sender = Some(sender);
}
backend::Event::Workspaces(mut workspaces) => {
workspaces.sort_by(|(_, w1), (_, w2)| w1.coordinates.cmp(&w2.coordinates));
// Coordinates are [x, y, ...] per the protocol convention.
// Sort in natural reading order (row/y first, then
// column/x) rather than plain lexicographic (which would
// sort by column first, scrambling a 2D grid). For 1D
// (linear) coordinates this is a no-op.
fn reading_order_key(coords: &[u32]) -> (u32, u32) {
(
coords.get(1).copied().unwrap_or(0),
coords.first().copied().unwrap_or(0),
)
}
workspaces.sort_by(|(_, w1), (_, w2)| {
reading_order_key(&w1.coordinates)
.cmp(&reading_order_key(&w2.coordinates))
});
let old_workspaces = mem::take(&mut self.workspaces);
let mut new_active = Vec::new();
for (outputs, workspace) in workspaces {
Expand Down Expand Up @@ -768,6 +787,9 @@ impl Application for App {
}
}
Msg::Close => {
if self.renaming.take().is_some() {
return Task::none();
}
return self.hide();
}
Msg::ActivateWorkspace(workspace_handle) => {
Expand Down Expand Up @@ -984,6 +1006,26 @@ impl Application for App {
));
}
}
Msg::StartRename(workspace_handle) => {
if let Some(workspace) = self.workspaces.for_handle(&workspace_handle) {
self.renaming = Some((workspace_handle, workspace.info.name.clone()));
}
}
Msg::RenameInputChanged(name) => {
if let Some((_, buf)) = &mut self.renaming {
*buf = name;
}
}
Msg::SubmitRename => {
if let Some((workspace_handle, name)) = self.renaming.take()
&& !name.trim().is_empty()
{
self.send_wayland_cmd(backend::Cmd::RenameWorkspace(workspace_handle, name));
}
}
Msg::CancelRename => {
self.renaming = None;
}
Msg::EnteredWorkspaceSidebarEntry(workspace_handle, entered) => {
if let Some(workspace) = self.workspaces.for_handle_mut(&workspace_handle) {
workspace.has_cursor = entered;
Expand Down
107 changes: 92 additions & 15 deletions src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,19 @@ pub(crate) fn layer_surface<'a>(
.flat_map(|t| &t.info.workspace)
.collect::<HashSet<_>>();
let layout = app.conf.workspace_config.workspace_layout;
let grid_columns = app.conf.workspace_config.workspace_grid_columns.max(1) as usize;
// track this rectangle
let sidebar = workspaces_sidebar(
app.workspaces.for_output(&surface.output),
&workspaces_with_toplevels,
&surface.output,
layout,
grid_columns,
app.drop_target.as_ref(),
drag_workspace,
window_id,
rectangle_track,
app.renaming.as_ref(),
);
let toplevels = toplevel_previews(
app.toplevels.0.iter().filter(|i| {
Expand Down Expand Up @@ -136,7 +139,8 @@ pub(crate) fn layer_surface<'a>(
cosmic::Element::from(toplevels)
};
let container = match layout {
WorkspaceLayout::Vertical => widget::layer_container(
// Grid places the workspace sidebar on the left like Vertical.
WorkspaceLayout::Vertical | WorkspaceLayout::Grid => widget::layer_container(
row![sidebar, toplevels]
.spacing(12)
.height(Length::Fill)
Expand Down Expand Up @@ -216,6 +220,24 @@ fn pin_button(workspace: &Workspace) -> cosmic::Element<'static, Msg> {
.into()
}

fn rename_button(workspace: &Workspace) -> cosmic::Element<'static, Msg> {
crate::widgets::visibility_wrapper(
widget::button::custom(
widget::icon::from_name("edit-symbolic")
.symbolic(true)
.size(16),
)
.padding([4, 8])
.on_press(Msg::StartRename(workspace.handle().clone())),
workspace.has_cursor
&& workspace
.info
.cosmic_capabilities
.contains(zcosmic_workspace_handle_v2::WorkspaceCapabilities::Rename),
)
.into()
}

fn workspace_item_appearance(
theme: &cosmic::Theme,
is_active: bool,
Expand Down Expand Up @@ -244,6 +266,7 @@ fn workspace_item(
layout: WorkspaceLayout,
is_drop_target: bool,
has_workspace_drag: bool,
renaming: Option<String>,
) -> cosmic::Element<'static, Msg> {
let (mut image, image_height, image_width) = if let Some(img) = workspace.img.as_ref() {
let is_rotated = matches!(
Expand Down Expand Up @@ -285,14 +308,37 @@ fn workspace_item(
)
};

let workspace_footer = row![
widget::space::horizontal().width(Length::Fixed(32.0)),
let is_renaming = renaming.is_some();
let label: cosmic::Element<'static, Msg> = if let Some(buf) = renaming {
widget::text_input("", buf)
.on_input(Msg::RenameInputChanged)
.on_submit(|_| Msg::SubmitRename)
.width(Length::Fill)
.into()
} else {
widget::text::body(fl!("workspace", number = workspace.info.name.as_str()))
.ellipsize(Ellipsize::Middle(EllipsizeHeightLimit::Lines(1)))
.apply(widget::container)
.center_x(Length::Fill),
pin_button(workspace),
];
.center_x(Length::Fill)
.into()
};
let workspace_footer = row![
widget::space::horizontal().width(Length::Fixed(32.0)),
label,
]
.push_maybe((!is_renaming).then(|| rename_button(workspace)))
.push(if is_renaming {
widget::button::custom(
widget::icon::from_name("window-close-symbolic")
.symbolic(true)
.size(16),
)
.padding([4, 8])
.on_press(Msg::CancelRename)
.into()
} else {
pin_button(workspace)
});

// Needed to prevent footer content getting pushed out when scaling on Vertical layout
if layout == WorkspaceLayout::Vertical {
Expand Down Expand Up @@ -357,7 +403,7 @@ fn workspace_drag_placeholder(
})
.padding(8);
let placeholder = crate::widgets::match_size(
workspace_item(other_workspace, other_output, layout, true, true),
workspace_item(other_workspace, other_output, layout, true, true, None),
placeholder,
);
dnd_destination_for_target(drop_target, placeholder.into(), Msg::DndWorkspaceDrop)
Expand All @@ -370,6 +416,7 @@ fn workspace_sidebar_entry<'a>(
is_drop_target: bool,
has_toplevels: bool,
has_workspace_drag: bool,
renaming: Option<String>,
) -> cosmic::Element<'a, Msg> {
/* XXX
let mouse_interaction = if is_drop_target {
Expand All @@ -384,6 +431,7 @@ fn workspace_sidebar_entry<'a>(
layout,
is_drop_target,
has_workspace_drag,
renaming,
);
let item = iced::widget::mouse_area(item)
.on_enter(Msg::EnteredWorkspaceSidebarEntry(
Expand Down Expand Up @@ -415,7 +463,7 @@ fn workspace_sidebar_entry<'a>(
DragSurface::Workspace(workspace.handle().clone()),
Some(workspace.dnd_source_id.clone()),
destination,
move || workspace_item(&workspace_clone, &output_clone, layout, false, true),
move || workspace_item(&workspace_clone, &output_clone, layout, false, true, None),
)
} else {
destination
Expand All @@ -428,10 +476,12 @@ fn workspaces_sidebar<'a>(
workspaces_with_toplevels: &HashSet<&backend::ExtWorkspaceHandleV1>,
output: &'a wl_output::WlOutput,
layout: WorkspaceLayout,
grid_columns: usize,
drop_target: Option<&DropTarget>,
drag_workspace: Option<&'a backend::ExtWorkspaceHandleV1>,
window_id: window::Id,
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
renaming: Option<&'a (backend::ExtWorkspaceHandleV1, String)>,
) -> cosmic::Element<'a, Msg> {
let mut sidebar_entries = Vec::new();
for workspace in workspaces {
Expand All @@ -447,7 +497,7 @@ fn workspaces_sidebar<'a>(
.width(Length::Shrink)
.height(Length::Shrink)
.into(),
move || workspace_item(&workspace_clone, &output_clone, layout, false, true),
move || workspace_item(&workspace_clone, &output_clone, layout, false, true, None),
);
sidebar_entries.push(source);
continue;
Expand Down Expand Up @@ -475,21 +525,45 @@ fn workspaces_sidebar<'a>(
{
sidebar_entries.push(workspace_drag_placeholder(workspace, output, layout));
}
let rename_buf = renaming
.filter(|(handle, _)| handle == workspace.handle())
.map(|(_, name)| name.clone());
sidebar_entries.push(workspace_sidebar_entry(
workspace,
output,
layout,
drop_target_is_workspace && drag_workspace.is_none(),
workspaces_with_toplevels.contains(workspace.handle()),
drag_workspace.is_some(),
rename_buf,
));
}
let (axis, width, height) = match layout {
WorkspaceLayout::Vertical => (Axis::Vertical, Length::Shrink, Length::Fill),
WorkspaceLayout::Horizontal => (Axis::Horizontal, Length::Fill, Length::Shrink),
let (width, height) = match layout {
WorkspaceLayout::Vertical | WorkspaceLayout::Grid => (Length::Shrink, Length::Fill),
WorkspaceLayout::Horizontal => (Length::Fill, Length::Shrink),
};
let sidebar_entries_container = match layout {
// Grid: chunk entries into rows of `grid_columns`, matching the
// compositor's 2D workspace arrangement.
WorkspaceLayout::Grid => {
let columns = grid_columns.max(1);
let mut grid = widget::grid().column_spacing(8).row_spacing(8);
for (i, entry) in sidebar_entries.into_iter().enumerate() {
if i > 0 && i % columns == 0 {
grid = grid.insert_row();
}
grid = grid.push(entry);
}
widget::container(grid).padding(8.0)
}
WorkspaceLayout::Vertical | WorkspaceLayout::Horizontal => {
let axis = match layout {
WorkspaceLayout::Horizontal => Axis::Horizontal,
_ => Axis::Vertical,
};
widget::container(crate::widgets::workspace_bar(sidebar_entries, axis)).padding(8.0)
}
};
let sidebar_entries_container =
widget::container(crate::widgets::workspace_bar(sidebar_entries, axis)).padding(8.0);

widget::container(
rectangle_track.container(
Expand Down Expand Up @@ -644,7 +718,10 @@ fn toplevel_previews<'a>(
rectangle_track: &rectangle_tracker::RectangleTracker<RectId>,
) -> cosmic::Element<'a, Msg> {
let (width, height) = match layout {
WorkspaceLayout::Vertical => (Length::FillPortion(4), Length::Fill),
// Grid places the workspace sidebar on the left like Vertical.
WorkspaceLayout::Vertical | WorkspaceLayout::Grid => {
(Length::FillPortion(4), Length::Fill)
}
WorkspaceLayout::Horizontal => (Length::Fill, Length::FillPortion(4)),
};
let entries = toplevels
Expand Down