Add support for workspace pinning and dragging
Workspaces can be pinned, and dragged to reorder or move to a different output. These features are enabled only if cosmic-workspace-v2 advertises the necessary protocol version and capabilities. The layout of the labels and pin buttons could be tweaked a bit still. Some hacks and workarounds are needed to get drag and drop working as desired. Something iced and libcosmic could potentially improve in the future. But this now seems fairly robust.
This commit is contained in:
parent
94ec10686e
commit
3c9a923f41
8 changed files with 585 additions and 95 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -1087,7 +1087,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-client-toolkit"
|
name = "cosmic-client-toolkit"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#6b05c2a157118979cb472a38455ba78ca9729196"
|
source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#bc4af9183e0967802d7fbe91ba811a29ca6a3b67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
"cosmic-protocols",
|
"cosmic-protocols",
|
||||||
|
|
@ -1151,7 +1151,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-protocols"
|
name = "cosmic-protocols"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#6b05c2a157118979cb472a38455ba78ca9729196"
|
source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#bc4af9183e0967802d7fbe91ba811a29ca6a3b67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
|
|
@ -1672,7 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3185,7 +3185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4539,7 +4539,7 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4552,7 +4552,7 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.9.4",
|
"linux-raw-sys 0.9.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5000,7 +5000,7 @@ dependencies = [
|
||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5844,7 +5844,7 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -80,5 +80,8 @@ pub enum Cmd {
|
||||||
ExtWorkspaceHandleV1,
|
ExtWorkspaceHandleV1,
|
||||||
wl_output::WlOutput,
|
wl_output::WlOutput,
|
||||||
),
|
),
|
||||||
|
MoveWorkspaceBefore(ExtWorkspaceHandleV1, ExtWorkspaceHandleV1),
|
||||||
|
MoveWorkspaceAfter(ExtWorkspaceHandleV1, ExtWorkspaceHandleV1),
|
||||||
ActivateWorkspace(ExtWorkspaceHandleV1),
|
ActivateWorkspace(ExtWorkspaceHandleV1),
|
||||||
|
SetWorkspacePinned(ExtWorkspaceHandleV1, bool),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
use calloop_wayland_source::WaylandSource;
|
use calloop_wayland_source::WaylandSource;
|
||||||
use cctk::{
|
use cctk::{
|
||||||
|
cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2,
|
||||||
screencopy::{CaptureSource, ScreencopyState},
|
screencopy::{CaptureSource, ScreencopyState},
|
||||||
sctk::{
|
sctk::{
|
||||||
self,
|
self,
|
||||||
|
|
@ -106,12 +107,68 @@ impl AppData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO version check
|
||||||
|
Cmd::MoveWorkspaceBefore(workspace_handle, other_workspace_handle) => {
|
||||||
|
if let Ok(workspace_manager) = self.workspace_state.workspace_manager().get() {
|
||||||
|
if let Some(cosmic_workspace) = self
|
||||||
|
.workspace_state
|
||||||
|
.workspaces()
|
||||||
|
.find(|w| w.handle == workspace_handle)
|
||||||
|
.and_then(|w| w.cosmic_handle.as_ref())
|
||||||
|
{
|
||||||
|
if cosmic_workspace.version()
|
||||||
|
>= zcosmic_workspace_handle_v2::REQ_MOVE_BEFORE_SINCE
|
||||||
|
{
|
||||||
|
cosmic_workspace.move_before(&other_workspace_handle, 0);
|
||||||
|
workspace_manager.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cmd::MoveWorkspaceAfter(workspace_handle, other_workspace_handle) => {
|
||||||
|
if let Ok(workspace_manager) = self.workspace_state.workspace_manager().get() {
|
||||||
|
if let Some(cosmic_workspace) = self
|
||||||
|
.workspace_state
|
||||||
|
.workspaces()
|
||||||
|
.find(|w| w.handle == workspace_handle)
|
||||||
|
.and_then(|w| w.cosmic_handle.as_ref())
|
||||||
|
{
|
||||||
|
if cosmic_workspace.version()
|
||||||
|
>= zcosmic_workspace_handle_v2::REQ_MOVE_AFTER_SINCE
|
||||||
|
{
|
||||||
|
cosmic_workspace.move_after(&other_workspace_handle, 0);
|
||||||
|
workspace_manager.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Cmd::ActivateWorkspace(workspace_handle) => {
|
Cmd::ActivateWorkspace(workspace_handle) => {
|
||||||
if let Ok(workspace_manager) = self.workspace_state.workspace_manager().get() {
|
if let Ok(workspace_manager) = self.workspace_state.workspace_manager().get() {
|
||||||
workspace_handle.activate();
|
workspace_handle.activate();
|
||||||
workspace_manager.commit();
|
workspace_manager.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Cmd::SetWorkspacePinned(workspace_handle, pinned) => {
|
||||||
|
if let Ok(workspace_manager) = self.workspace_state.workspace_manager().get() {
|
||||||
|
if let Some(cosmic_workspace) = self
|
||||||
|
.workspace_state
|
||||||
|
.workspaces()
|
||||||
|
.find(|w| w.handle == workspace_handle)
|
||||||
|
.and_then(|w| w.cosmic_handle.as_ref())
|
||||||
|
{
|
||||||
|
if cosmic_workspace.version() >= zcosmic_workspace_handle_v2::REQ_PIN_SINCE
|
||||||
|
{
|
||||||
|
// TODO check capability
|
||||||
|
if pinned {
|
||||||
|
cosmic_workspace.pin();
|
||||||
|
} else {
|
||||||
|
cosmic_workspace.unpin();
|
||||||
|
}
|
||||||
|
workspace_manager.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
src/dnd.rs
30
src/dnd.rs
|
|
@ -92,10 +92,36 @@ impl TryFrom<(Vec<u8>, std::string::String)> for DragWorkspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO name?
|
||||||
|
pub enum Drag {
|
||||||
|
Toplevel,
|
||||||
|
Workspace,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cosmic::iced::clipboard::mime::AllowedMimeTypes for Drag {
|
||||||
|
fn allowed() -> Cow<'static, [String]> {
|
||||||
|
vec![TOPLEVEL_MIME.clone(), WORKSPACE_MIME.clone()].into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(Vec<u8>, std::string::String)> for Drag {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from((_bytes, mime_type): (Vec<u8>, String)) -> Result<Self, ()> {
|
||||||
|
if mime_type == *TOPLEVEL_MIME {
|
||||||
|
Ok(Self::Toplevel)
|
||||||
|
} else if mime_type == *WORKSPACE_MIME {
|
||||||
|
Ok(Self::Workspace)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum DropTarget {
|
pub enum DropTarget {
|
||||||
WorkspaceSidebarEntry(ExtWorkspaceHandleV1, wl_output::WlOutput),
|
WorkspaceSidebarEntry(ExtWorkspaceHandleV1, wl_output::WlOutput),
|
||||||
|
WorkspaceSidebarDragPlaceholder(ExtWorkspaceHandleV1, wl_output::WlOutput),
|
||||||
OutputToplevels(ExtWorkspaceHandleV1, wl_output::WlOutput),
|
OutputToplevels(ExtWorkspaceHandleV1, wl_output::WlOutput),
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
WorkspacesBar(wl_output::WlOutput),
|
WorkspacesBar(wl_output::WlOutput),
|
||||||
|
|
@ -112,6 +138,10 @@ impl DropTarget {
|
||||||
let id = workspace.id().protocol_id();
|
let id = workspace.id().protocol_id();
|
||||||
(u64::from(discriminant) << 32) | u64::from(id)
|
(u64::from(discriminant) << 32) | u64::from(id)
|
||||||
}
|
}
|
||||||
|
Self::WorkspaceSidebarDragPlaceholder(workspace, _output) => {
|
||||||
|
let id = workspace.id().protocol_id();
|
||||||
|
(u64::from(discriminant) << 32) | u64::from(id)
|
||||||
|
}
|
||||||
Self::OutputToplevels(_workspace, output) => {
|
Self::OutputToplevels(_workspace, output) => {
|
||||||
let id = output.id().protocol_id();
|
let id = output.id().protocol_id();
|
||||||
(u64::from(discriminant) << 32) | u64::from(id)
|
(u64::from(discriminant) << 32) | u64::from(id)
|
||||||
|
|
|
||||||
89
src/main.rs
89
src/main.rs
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
use cctk::{
|
use cctk::{
|
||||||
cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
|
cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
|
||||||
|
cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2,
|
||||||
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
|
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
|
||||||
wayland_client::{protocol::wl_output, Connection, Proxy},
|
wayland_client::{protocol::wl_output, Connection, Proxy},
|
||||||
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
|
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
|
||||||
|
|
@ -15,6 +16,7 @@ use cosmic::{
|
||||||
cctk, dbus_activation,
|
cctk, dbus_activation,
|
||||||
iced::{
|
iced::{
|
||||||
self,
|
self,
|
||||||
|
clipboard::dnd::{DndEvent, SourceEvent},
|
||||||
event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
|
event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
|
||||||
keyboard::key::{Key, Named},
|
keyboard::key::{Key, Named},
|
||||||
mouse::ScrollDelta,
|
mouse::ScrollDelta,
|
||||||
|
|
@ -111,6 +113,8 @@ enum Msg {
|
||||||
BgConfig(cosmic_bg_config::state::State),
|
BgConfig(cosmic_bg_config::state::State),
|
||||||
UpdateToplevelIcon(String, Option<PathBuf>),
|
UpdateToplevelIcon(String, Option<PathBuf>),
|
||||||
OnScroll(wl_output::WlOutput, ScrollDelta),
|
OnScroll(wl_output::WlOutput, ScrollDelta),
|
||||||
|
TogglePinned(ExtWorkspaceHandleV1),
|
||||||
|
EnteredWorkspaceSidebarEntry(ExtWorkspaceHandleV1, bool),
|
||||||
Ignore,
|
Ignore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +124,8 @@ struct Workspace {
|
||||||
// img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>,
|
// img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>,
|
||||||
img: Option<backend::CaptureImage>,
|
img: Option<backend::CaptureImage>,
|
||||||
outputs: HashSet<wl_output::WlOutput>,
|
outputs: HashSet<wl_output::WlOutput>,
|
||||||
|
has_cursor: bool,
|
||||||
|
dnd_source_id: iced::id::Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
|
|
@ -132,6 +138,12 @@ impl Workspace {
|
||||||
.state
|
.state
|
||||||
.contains(ext_workspace_handle_v1::State::Active)
|
.contains(ext_workspace_handle_v1::State::Active)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_pinned(&self) -> bool {
|
||||||
|
self.info
|
||||||
|
.cosmic_state
|
||||||
|
.contains(zcosmic_workspace_handle_v2::State::Pinned)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -396,17 +408,20 @@ impl Application for App {
|
||||||
self.workspaces = Vec::new();
|
self.workspaces = Vec::new();
|
||||||
for (outputs, workspace) in workspaces {
|
for (outputs, workspace) in workspaces {
|
||||||
// XXX efficiency
|
// XXX efficiency
|
||||||
#[allow(clippy::mutable_key_type)]
|
let old_workspace = old_workspaces
|
||||||
let img = old_workspaces
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|i| *i.handle() == workspace.handle)
|
.find(|i| *i.handle() == workspace.handle);
|
||||||
.map(|i| i.img.clone())
|
let img = old_workspace.map(|i| i.img.clone()).unwrap_or_default();
|
||||||
.unwrap_or_default();
|
let has_cursor = old_workspace.is_some_and(|w| w.has_cursor);
|
||||||
|
let dnd_source_id = old_workspace
|
||||||
|
.map_or_else(iced::id::Id::unique, |w| w.dnd_source_id.clone());
|
||||||
|
|
||||||
self.workspaces.push(Workspace {
|
self.workspaces.push(Workspace {
|
||||||
info: workspace,
|
info: workspace,
|
||||||
outputs,
|
outputs,
|
||||||
img,
|
img,
|
||||||
|
has_cursor,
|
||||||
|
dnd_source_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.update_capture_filter();
|
self.update_capture_filter();
|
||||||
|
|
@ -530,7 +545,11 @@ impl Application for App {
|
||||||
output,
|
output,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(DropTarget::WorkspacesBar(_)) | None => {}
|
Some(
|
||||||
|
DropTarget::WorkspacesBar(_)
|
||||||
|
| DropTarget::WorkspaceSidebarDragPlaceholder(_, _),
|
||||||
|
)
|
||||||
|
| None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -626,7 +645,58 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::DndWorkspaceDrag => {}
|
Msg::DndWorkspaceDrag => {}
|
||||||
Msg::DndWorkspaceDrop(_workspace) => {}
|
Msg::DndWorkspaceDrop(_workspace) => {
|
||||||
|
if let Some((DragSurface::Workspace(handle), _)) = &self.drag_surface {
|
||||||
|
match self.drop_target.take() {
|
||||||
|
Some(
|
||||||
|
DropTarget::WorkspaceSidebarEntry(other_handle, _output)
|
||||||
|
| DropTarget::WorkspaceSidebarDragPlaceholder(other_handle, _output),
|
||||||
|
) => {
|
||||||
|
let workspace = self.workspaces.iter().find(|i| i.handle() == handle);
|
||||||
|
let other_workspace =
|
||||||
|
self.workspaces.iter().find(|i| *i.handle() == other_handle);
|
||||||
|
if let (Some(workspace), Some(other_workspace)) =
|
||||||
|
(workspace, other_workspace)
|
||||||
|
{
|
||||||
|
if workspace.outputs == other_workspace.outputs
|
||||||
|
&& workspace.info.coordinates[0] + 1
|
||||||
|
== other_workspace.info.coordinates[0]
|
||||||
|
{
|
||||||
|
// Workspace is already in requested position
|
||||||
|
} else {
|
||||||
|
self.send_wayland_cmd(backend::Cmd::MoveWorkspaceBefore(
|
||||||
|
handle.clone(),
|
||||||
|
other_handle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(DropTarget::OutputToplevels(_, _) | DropTarget::WorkspacesBar(_))
|
||||||
|
| None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg::TogglePinned(workspace_handle) => {
|
||||||
|
if let Some(workspace) = self
|
||||||
|
.workspaces
|
||||||
|
.iter()
|
||||||
|
.find(|w| *w.handle() == workspace_handle)
|
||||||
|
{
|
||||||
|
self.send_wayland_cmd(backend::Cmd::SetWorkspacePinned(
|
||||||
|
workspace_handle,
|
||||||
|
!workspace.is_pinned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg::EnteredWorkspaceSidebarEntry(workspace_handle, entered) => {
|
||||||
|
if let Some(workspace) = self
|
||||||
|
.workspaces
|
||||||
|
.iter_mut()
|
||||||
|
.find(|w| *w.handle() == workspace_handle)
|
||||||
|
{
|
||||||
|
workspace.has_cursor = entered;
|
||||||
|
}
|
||||||
|
}
|
||||||
Msg::Ignore => {}
|
Msg::Ignore => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -656,6 +726,11 @@ impl Application for App {
|
||||||
modified_key: _,
|
modified_key: _,
|
||||||
physical_key: _,
|
physical_key: _,
|
||||||
}) => Some(Msg::Close),
|
}) => Some(Msg::Close),
|
||||||
|
// XXX Workaround for `on_finish`/`on_cancel` not being called, seemingly
|
||||||
|
// due to state diffing behavior.
|
||||||
|
iced::Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
|
||||||
|
Some(Msg::SourceFinished)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
let config_subscription = cosmic_config::config_subscription::<_, CosmicWorkspacesConfig>(
|
let config_subscription = cosmic_config::config_subscription::<_, CosmicWorkspacesConfig>(
|
||||||
|
|
|
||||||
338
src/view/mod.rs
338
src/view/mod.rs
|
|
@ -1,29 +1,59 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
cctk::{
|
cctk::{
|
||||||
cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
|
cosmic_protocols::{
|
||||||
|
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
|
||||||
|
workspace::v2::client::zcosmic_workspace_handle_v2,
|
||||||
|
},
|
||||||
wayland_client::protocol::wl_output,
|
wayland_client::protocol::wl_output,
|
||||||
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
|
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
|
||||||
},
|
},
|
||||||
iced::{
|
iced::{
|
||||||
self,
|
self,
|
||||||
advanced::layout::flex::Axis,
|
advanced::layout::flex::Axis,
|
||||||
clipboard::mime::AllowedMimeTypes,
|
clipboard::mime::{AllowedMimeTypes, AsMimeTypes},
|
||||||
widget::{column, row},
|
widget::{column, row},
|
||||||
Border, Length,
|
Border, Length,
|
||||||
},
|
},
|
||||||
iced_core::{text::Wrapping, Shadow},
|
iced_core::{text::Wrapping, Shadow},
|
||||||
iced_winit::platform_specific::wayland::subsurface_widget::Subsurface,
|
iced_winit::platform_specific::wayland::subsurface_widget::Subsurface,
|
||||||
widget, Apply,
|
widget::{self, Widget},
|
||||||
|
Apply,
|
||||||
};
|
};
|
||||||
use cosmic_comp_config::workspace::WorkspaceLayout;
|
use cosmic_comp_config::workspace::WorkspaceLayout;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{self, CaptureImage},
|
backend::{self, CaptureImage},
|
||||||
dnd::{DragSurface, DragToplevel, DragWorkspace, DropTarget},
|
dnd::{Drag, DragSurface, DragToplevel, DragWorkspace, DropTarget},
|
||||||
App, LayerSurface, Msg, Toplevel, Workspace,
|
App, LayerSurface, Msg, Toplevel, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn dnd_source_with_drag_surface<D: AsMimeTypes + Send + Clone + 'static>(
|
||||||
|
drag_content: D,
|
||||||
|
drag_surface: DragSurface,
|
||||||
|
id: Option<iced::id::Id>,
|
||||||
|
child: cosmic::Element<'_, Msg>,
|
||||||
|
drag_icon: impl Fn() -> cosmic::Element<'static, Msg> + 'static,
|
||||||
|
) -> cosmic::Element<'_, Msg> {
|
||||||
|
let mut source = cosmic::widget::dnd_source(child)
|
||||||
|
.drag_threshold(5.)
|
||||||
|
.drag_content(move || drag_content.clone())
|
||||||
|
.drag_icon(move |offset| {
|
||||||
|
(
|
||||||
|
drag_icon().map(|_| ()),
|
||||||
|
cosmic::iced_core::widget::tree::State::None,
|
||||||
|
-offset,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_start(Some(Msg::StartDrag(drag_surface)))
|
||||||
|
.on_finish(Some(Msg::SourceFinished))
|
||||||
|
.on_cancel(Some(Msg::SourceFinished));
|
||||||
|
if let Some(id) = id {
|
||||||
|
source.set_id(id);
|
||||||
|
}
|
||||||
|
source.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn dnd_destination_for_target<T>(
|
fn dnd_destination_for_target<T>(
|
||||||
target: DropTarget,
|
target: DropTarget,
|
||||||
child: cosmic::Element<'_, Msg>,
|
child: cosmic::Element<'_, Msg>,
|
||||||
|
|
@ -50,22 +80,31 @@ pub(crate) fn layer_surface<'a>(
|
||||||
app: &'a App,
|
app: &'a App,
|
||||||
surface: &'a LayerSurface,
|
surface: &'a LayerSurface,
|
||||||
) -> cosmic::Element<'a, Msg> {
|
) -> cosmic::Element<'a, Msg> {
|
||||||
let mut drop_target = None;
|
|
||||||
if let Some(DropTarget::WorkspaceSidebarEntry(workspace, output)) = &app.drop_target {
|
|
||||||
if output == &surface.output {
|
|
||||||
drop_target = Some(workspace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut drag_toplevel = None;
|
let mut drag_toplevel = None;
|
||||||
if let Some((DragSurface::Toplevel(handle), _)) = &app.drag_surface {
|
let mut drag_workspace = None;
|
||||||
drag_toplevel = Some(handle);
|
match &app.drag_surface {
|
||||||
|
Some((DragSurface::Toplevel(handle), _)) => {
|
||||||
|
drag_toplevel = Some(handle);
|
||||||
|
}
|
||||||
|
Some((DragSurface::Workspace(handle), _)) => {
|
||||||
|
drag_workspace = Some(handle);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
#[allow(clippy::mutable_key_type)]
|
||||||
|
let workspaces_with_toplevels = app
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| &t.info.workspace)
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
let layout = app.conf.workspace_config.workspace_layout;
|
let layout = app.conf.workspace_config.workspace_layout;
|
||||||
let sidebar = workspaces_sidebar(
|
let sidebar = workspaces_sidebar(
|
||||||
app.workspaces_for_output(&surface.output),
|
app.workspaces_for_output(&surface.output),
|
||||||
|
&workspaces_with_toplevels,
|
||||||
&surface.output,
|
&surface.output,
|
||||||
layout,
|
layout,
|
||||||
drop_target,
|
app.drop_target.as_ref(),
|
||||||
|
drag_workspace,
|
||||||
);
|
);
|
||||||
let toplevels = toplevel_previews(
|
let toplevels = toplevel_previews(
|
||||||
app.toplevels.iter().filter(|i| {
|
app.toplevels.iter().filter(|i| {
|
||||||
|
|
@ -122,6 +161,57 @@ fn close_button(on_press: Msg) -> cosmic::Element<'static, Msg> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pin_button_style(theme: &cosmic::Theme, is_pinned: bool) -> cosmic::widget::button::Style {
|
||||||
|
let bg_color = if is_pinned {
|
||||||
|
theme.cosmic().accent.base.into()
|
||||||
|
} else {
|
||||||
|
theme.cosmic().primary.base.into()
|
||||||
|
};
|
||||||
|
let icon_color = if is_pinned {
|
||||||
|
theme.cosmic().accent.on.into()
|
||||||
|
} else {
|
||||||
|
theme.cosmic().primary.on.into()
|
||||||
|
};
|
||||||
|
cosmic::widget::button::Style {
|
||||||
|
icon_color: Some(icon_color),
|
||||||
|
background: Some(iced::Background::Color(bg_color)),
|
||||||
|
border_radius: theme.cosmic().corner_radii.radius_s.into(),
|
||||||
|
..cosmic::widget::button::Style::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pin_button(workspace: &Workspace) -> cosmic::Element<'static, Msg> {
|
||||||
|
let is_pinned = workspace.is_pinned();
|
||||||
|
crate::widgets::visibility_wrapper(
|
||||||
|
widget::button::custom(
|
||||||
|
widget::icon::from_name("pin-symbolic")
|
||||||
|
.symbolic(true)
|
||||||
|
.size(16), //.style(|theme, status| todo!())
|
||||||
|
)
|
||||||
|
//.class(cosmic::theme::Button::Icon)
|
||||||
|
//.class(cosmic::theme::Button::Image)
|
||||||
|
.class(cosmic::theme::Button::Custom {
|
||||||
|
// TODO adjust state for hover, etc.
|
||||||
|
active: Box::new(move |_, theme| pin_button_style(theme, is_pinned)),
|
||||||
|
disabled: Box::new(move |theme| pin_button_style(theme, is_pinned)),
|
||||||
|
hovered: Box::new(move |_, theme| pin_button_style(theme, is_pinned)),
|
||||||
|
pressed: Box::new(move |_, theme| pin_button_style(theme, is_pinned)),
|
||||||
|
})
|
||||||
|
//.class(cosmic::theme::Button::Standard)
|
||||||
|
// TODO style selected correctly
|
||||||
|
.selected(workspace.is_pinned())
|
||||||
|
.on_press(Msg::TogglePinned(workspace.handle().clone())),
|
||||||
|
// Show pin button only if hovered or pinned; but allocate space the same way
|
||||||
|
// regardless
|
||||||
|
(workspace.has_cursor || workspace.is_pinned())
|
||||||
|
&& workspace
|
||||||
|
.info
|
||||||
|
.cosmic_capabilities
|
||||||
|
.contains(zcosmic_workspace_handle_v2::WorkspaceCapabilities::Pin),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn workspace_item_appearance(
|
fn workspace_item_appearance(
|
||||||
theme: &cosmic::Theme,
|
theme: &cosmic::Theme,
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
|
|
@ -149,8 +239,9 @@ fn workspace_item(
|
||||||
_output: &wl_output::WlOutput,
|
_output: &wl_output::WlOutput,
|
||||||
layout: WorkspaceLayout,
|
layout: WorkspaceLayout,
|
||||||
is_drop_target: bool,
|
is_drop_target: bool,
|
||||||
|
has_workspace_drag: bool,
|
||||||
) -> cosmic::Element<'static, Msg> {
|
) -> cosmic::Element<'static, Msg> {
|
||||||
let (image, image_height) = if let Some(img) = workspace.img.as_ref() {
|
let (mut image, image_height) = if let Some(img) = workspace.img.as_ref() {
|
||||||
let is_rotated = matches!(
|
let is_rotated = matches!(
|
||||||
img.transform,
|
img.transform,
|
||||||
wl_output::Transform::_90
|
wl_output::Transform::_90
|
||||||
|
|
@ -188,28 +279,38 @@ fn workspace_item(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace_name = widget::text::body(fl!(
|
let workspace_name = row![
|
||||||
"workspace",
|
widget::text::body(fl!(
|
||||||
HashMap::from([("number", &workspace.info.name)])
|
"workspace",
|
||||||
));
|
HashMap::from([("number", &workspace.info.name)])
|
||||||
|
))
|
||||||
|
.width(Length::Fill) // XXX mades workspace bar fill screen
|
||||||
|
.align_x(iced::Alignment::Center),
|
||||||
|
pin_button(workspace),
|
||||||
|
];
|
||||||
|
|
||||||
// Needed to prevent text getting pushed out when scaling on Vertical layout
|
// Needed to prevent text getting pushed out when scaling on Vertical layout
|
||||||
let content = match layout {
|
if layout == WorkspaceLayout::Vertical {
|
||||||
WorkspaceLayout::Horizontal => column![image, workspace_name]
|
image = image.height(Length::Fill);
|
||||||
.align_x(iced::Alignment::Center)
|
}
|
||||||
.spacing(4)
|
let mut content = crate::widgets::size_cross_nth(
|
||||||
.apply(widget::container),
|
vec![
|
||||||
WorkspaceLayout::Vertical => column![image.height(Length::Fill), workspace_name]
|
image.into(),
|
||||||
.align_x(iced::Alignment::Center)
|
iced::widget::Space::with_height(4.0).into(),
|
||||||
.spacing(4)
|
workspace_name.into(),
|
||||||
.apply(widget::container)
|
],
|
||||||
.max_height(image_height + 21.0 + 4.0), // text height + spacing
|
Axis::Vertical,
|
||||||
};
|
0, // Size container to match image size
|
||||||
|
)
|
||||||
|
.apply(widget::container);
|
||||||
|
if layout == WorkspaceLayout::Vertical {
|
||||||
|
content = content.max_height(image_height + 21.0 + 4.0); // text height + spacing
|
||||||
|
}
|
||||||
|
|
||||||
let is_active = workspace.is_active();
|
let is_active = workspace.is_active() && !has_workspace_drag;
|
||||||
// TODO editable name?
|
// TODO editable name?
|
||||||
let mut button = widget::button::custom(content)
|
let mut button = widget::button::custom(content)
|
||||||
.selected(workspace.is_active())
|
.selected(is_active)
|
||||||
.class(cosmic::theme::Button::Custom {
|
.class(cosmic::theme::Button::Custom {
|
||||||
active: Box::new(move |_focused, theme| {
|
active: Box::new(move |_focused, theme| {
|
||||||
workspace_item_appearance(theme, is_active, is_drop_target)
|
workspace_item_appearance(theme, is_active, is_drop_target)
|
||||||
|
|
@ -235,11 +336,37 @@ fn workspace_item(
|
||||||
button.into()
|
button.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn workspace_drag_placeholder(
|
||||||
|
other_workspace: &Workspace,
|
||||||
|
other_output: &wl_output::WlOutput,
|
||||||
|
layout: WorkspaceLayout,
|
||||||
|
) -> cosmic::Element<'static, Msg> {
|
||||||
|
let drop_target = DropTarget::WorkspaceSidebarDragPlaceholder(
|
||||||
|
other_workspace.handle().clone(),
|
||||||
|
other_output.clone(),
|
||||||
|
);
|
||||||
|
let placeholder = widget::button::custom(widget::Space::new(Length::Fill, Length::Fill))
|
||||||
|
.class(cosmic::theme::Button::Custom {
|
||||||
|
active: Box::new(|_, _| unreachable!()),
|
||||||
|
disabled: Box::new(|theme| workspace_item_appearance(theme, true, true)),
|
||||||
|
hovered: Box::new(|_, _| unreachable!()),
|
||||||
|
pressed: Box::new(|_, _| unreachable!()),
|
||||||
|
})
|
||||||
|
.padding(8);
|
||||||
|
let placeholder = crate::widgets::match_size(
|
||||||
|
workspace_item(other_workspace, other_output, layout, true, true),
|
||||||
|
placeholder,
|
||||||
|
);
|
||||||
|
dnd_destination_for_target(drop_target, placeholder.into(), Msg::DndWorkspaceDrop)
|
||||||
|
}
|
||||||
|
|
||||||
fn workspace_sidebar_entry<'a>(
|
fn workspace_sidebar_entry<'a>(
|
||||||
workspace: &'a Workspace,
|
workspace: &'a Workspace,
|
||||||
output: &'a wl_output::WlOutput,
|
output: &'a wl_output::WlOutput,
|
||||||
layout: WorkspaceLayout,
|
layout: WorkspaceLayout,
|
||||||
is_drop_target: bool,
|
is_drop_target: bool,
|
||||||
|
has_toplevels: bool,
|
||||||
|
has_workspace_drag: bool,
|
||||||
) -> cosmic::Element<'a, Msg> {
|
) -> cosmic::Element<'a, Msg> {
|
||||||
/* XXX
|
/* XXX
|
||||||
let mouse_interaction = if is_drop_target {
|
let mouse_interaction = if is_drop_target {
|
||||||
|
|
@ -248,45 +375,107 @@ fn workspace_sidebar_entry<'a>(
|
||||||
iced::mouse::Interaction::Idle
|
iced::mouse::Interaction::Idle
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
let item = workspace_item(workspace, output, layout, is_drop_target);
|
let item = workspace_item(
|
||||||
/* TODO allow moving workspaces (needs compositor support)
|
workspace,
|
||||||
|
output,
|
||||||
|
layout,
|
||||||
|
is_drop_target,
|
||||||
|
has_workspace_drag,
|
||||||
|
);
|
||||||
|
let item = iced::widget::mouse_area(item)
|
||||||
|
.on_enter(Msg::EnteredWorkspaceSidebarEntry(
|
||||||
|
workspace.handle().clone(),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.on_exit(Msg::EnteredWorkspaceSidebarEntry(
|
||||||
|
workspace.handle().clone(),
|
||||||
|
false,
|
||||||
|
));
|
||||||
let workspace_clone = workspace.clone(); // TODO avoid clone
|
let workspace_clone = workspace.clone(); // TODO avoid clone
|
||||||
let output_clone = output.clone();
|
let output_clone = output.clone();
|
||||||
let source = cosmic::widget::dnd_source(item)
|
let drop_target = DropTarget::WorkspaceSidebarEntry(workspace.handle().clone(), output.clone());
|
||||||
.drag_threshold(5.)
|
let destination =
|
||||||
.drag_content(|| DragWorkspace {})
|
dnd_destination_for_target(drop_target, item.into(), |drag: Drag| match drag {
|
||||||
.drag_icon(move |offset| {
|
Drag::Toplevel => Msg::DndToplevelDrop(DragToplevel {}),
|
||||||
(
|
Drag::Workspace => Msg::DndWorkspaceDrop(DragWorkspace {}),
|
||||||
workspace_item(&workspace_clone, &output_clone, false).map(|_| ()),
|
});
|
||||||
cosmic::iced_core::widget::tree::State::None,
|
// Cosmic-comp auto-removes workspaces that aren't pinned and don't have toplevels when they
|
||||||
-offset,
|
// aren't the last workspace. So it shouldn't be possible to drag.
|
||||||
)
|
if (has_toplevels || workspace.is_pinned())
|
||||||
})
|
&& workspace
|
||||||
.on_start(Some(Msg::StartDrag(DragSurface::Workspace(
|
.info
|
||||||
workspace.handle.clone(),
|
.cosmic_capabilities
|
||||||
))))
|
.contains(zcosmic_workspace_handle_v2::WorkspaceCapabilities::Move)
|
||||||
.on_finish(Some(Msg::SourceFinished))
|
{
|
||||||
.on_cancel(Some(Msg::SourceFinished))
|
dnd_source_with_drag_surface(
|
||||||
.into();
|
DragWorkspace {},
|
||||||
*/
|
DragSurface::Workspace(workspace.handle().clone()),
|
||||||
//crate::widgets::mouse_interaction_wrapper(
|
Some(workspace.dnd_source_id.clone()),
|
||||||
// mouse_interaction,
|
destination,
|
||||||
dnd_destination_for_target(
|
move || workspace_item(&workspace_clone, &output_clone, layout, false, true),
|
||||||
DropTarget::WorkspaceSidebarEntry(workspace.handle().clone(), output.clone()),
|
)
|
||||||
item,
|
} else {
|
||||||
Msg::DndToplevelDrop,
|
destination
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::mutable_key_type)]
|
||||||
fn workspaces_sidebar<'a>(
|
fn workspaces_sidebar<'a>(
|
||||||
workspaces: impl Iterator<Item = &'a Workspace>,
|
workspaces: impl Iterator<Item = &'a Workspace>,
|
||||||
|
workspaces_with_toplevels: &HashSet<&backend::ExtWorkspaceHandleV1>,
|
||||||
output: &'a wl_output::WlOutput,
|
output: &'a wl_output::WlOutput,
|
||||||
layout: WorkspaceLayout,
|
layout: WorkspaceLayout,
|
||||||
drop_target: Option<&backend::ExtWorkspaceHandleV1>,
|
drop_target: Option<&DropTarget>,
|
||||||
|
drag_workspace: Option<&'a backend::ExtWorkspaceHandleV1>,
|
||||||
) -> cosmic::Element<'a, Msg> {
|
) -> cosmic::Element<'a, Msg> {
|
||||||
let sidebar_entries = workspaces
|
let mut sidebar_entries = Vec::new();
|
||||||
.map(|w| workspace_sidebar_entry(w, output, layout, drop_target == Some(w.handle())))
|
for workspace in workspaces {
|
||||||
.collect();
|
// XXX Need dnd source with same id for drag to work; but give it 0x0 size
|
||||||
|
if drag_workspace == Some(workspace.handle()) {
|
||||||
|
let workspace_clone = workspace.clone();
|
||||||
|
let output_clone = output.clone();
|
||||||
|
let source = dnd_source_with_drag_surface(
|
||||||
|
DragWorkspace {},
|
||||||
|
DragSurface::Workspace(workspace.handle().clone()),
|
||||||
|
Some(workspace.dnd_source_id.clone()),
|
||||||
|
widget::Space::new(Length::Shrink, Length::Shrink).into(),
|
||||||
|
move || workspace_item(&workspace_clone, &output_clone, layout, false, true),
|
||||||
|
);
|
||||||
|
sidebar_entries.push(source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut drop_target_is_workspace = false;
|
||||||
|
let mut drop_target_is_placeholder = false;
|
||||||
|
match drop_target {
|
||||||
|
Some(DropTarget::WorkspaceSidebarEntry(w, o))
|
||||||
|
if (w, o) == (workspace.handle(), output) =>
|
||||||
|
{
|
||||||
|
drop_target_is_workspace = true;
|
||||||
|
}
|
||||||
|
Some(DropTarget::WorkspaceSidebarDragPlaceholder(w, o))
|
||||||
|
if (w, o) == (workspace.handle(), output) =>
|
||||||
|
{
|
||||||
|
drop_target_is_placeholder = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if drag_workspace.is_some()
|
||||||
|
&& drag_workspace != Some(workspace.handle())
|
||||||
|
&& (drop_target_is_workspace || drop_target_is_placeholder)
|
||||||
|
{
|
||||||
|
sidebar_entries.push(workspace_drag_placeholder(workspace, output, layout));
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let (axis, width, height) = match layout {
|
let (axis, width, height) = match layout {
|
||||||
WorkspaceLayout::Vertical => (Axis::Vertical, Length::Shrink, Length::Fill),
|
WorkspaceLayout::Vertical => (Axis::Vertical, Length::Shrink, Length::Fill),
|
||||||
WorkspaceLayout::Horizontal => (Axis::Horizontal, Length::Fill, Length::Shrink),
|
WorkspaceLayout::Horizontal => (Axis::Horizontal, Length::Fill, Length::Shrink),
|
||||||
|
|
@ -396,24 +585,13 @@ fn toplevel_previews_entry(
|
||||||
!is_being_dragged,
|
!is_being_dragged,
|
||||||
);
|
);
|
||||||
let toplevel2 = toplevel.clone();
|
let toplevel2 = toplevel.clone();
|
||||||
cosmic::widget::dnd_source::<_, DragToplevel>(preview)
|
dnd_source_with_drag_surface(
|
||||||
.drag_threshold(5.)
|
DragToplevel {},
|
||||||
.drag_content(|| DragToplevel {})
|
DragSurface::Toplevel(toplevel.handle.clone()),
|
||||||
// XXX State?
|
None,
|
||||||
.drag_icon(move |offset| {
|
preview.into(),
|
||||||
(
|
move || toplevel_preview(&toplevel2, true),
|
||||||
toplevel_preview(&toplevel2, true).map(|_| ()),
|
)
|
||||||
cosmic::iced_core::widget::tree::State::None,
|
|
||||||
-offset,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on_start(Some(Msg::StartDrag(
|
|
||||||
//size,
|
|
||||||
DragSurface::Toplevel(toplevel.handle.clone()),
|
|
||||||
)))
|
|
||||||
.on_finish(Some(Msg::SourceFinished))
|
|
||||||
.on_cancel(Some(Msg::SourceFinished))
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toplevel_previews<'a>(
|
fn toplevel_previews<'a>(
|
||||||
|
|
|
||||||
145
src/widgets/match_size.rs
Normal file
145
src/widgets/match_size.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
//! Show one surface, sized to match the size of another (invisible) widget
|
||||||
|
|
||||||
|
use cosmic::iced::{
|
||||||
|
advanced::{
|
||||||
|
layout, mouse, renderer,
|
||||||
|
widget::{Operation, Tree},
|
||||||
|
Clipboard, Layout, Shell, Widget,
|
||||||
|
},
|
||||||
|
event::{self, Event},
|
||||||
|
Length, Rectangle, Size,
|
||||||
|
};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub fn match_size<
|
||||||
|
'a,
|
||||||
|
Msg,
|
||||||
|
T1: Into<cosmic::Element<'a, Msg>>,
|
||||||
|
T2: Into<cosmic::Element<'a, Msg>>,
|
||||||
|
>(
|
||||||
|
matched: T1,
|
||||||
|
shown: T2,
|
||||||
|
) -> MatchSize<'a, Msg> {
|
||||||
|
MatchSize {
|
||||||
|
matched: matched.into(),
|
||||||
|
shown: shown.into(),
|
||||||
|
_msg: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MatchSize<'a, Msg> {
|
||||||
|
matched: cosmic::Element<'a, Msg>,
|
||||||
|
shown: cosmic::Element<'a, Msg>,
|
||||||
|
_msg: PhantomData<Msg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for MatchSize<'_, Msg> {
|
||||||
|
delegate::delegate! {
|
||||||
|
to self.matched.as_widget() {
|
||||||
|
fn size(&self) -> Size<Length>;
|
||||||
|
fn size_hint(&self) -> Size<Length>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &cosmic::Renderer,
|
||||||
|
operation: &mut dyn Operation<()>,
|
||||||
|
) {
|
||||||
|
self.matched
|
||||||
|
.as_widget()
|
||||||
|
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||||
|
self.shown
|
||||||
|
.as_widget()
|
||||||
|
.operate(&mut tree.children[1], layout, renderer, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &cosmic::Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Msg>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> event::Status {
|
||||||
|
self.shown.as_widget_mut().on_event(
|
||||||
|
&mut tree.children[1],
|
||||||
|
event,
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
viewport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &cosmic::Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.shown.as_widget().mouse_interaction(
|
||||||
|
&tree.children[1],
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
renderer: &cosmic::Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
// TODO?
|
||||||
|
self.matched
|
||||||
|
.as_widget()
|
||||||
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut cosmic::Renderer,
|
||||||
|
theme: &cosmic::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.shown.as_widget().draw(
|
||||||
|
&tree.children[1],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
vec![Tree::new(&self.matched), Tree::new(&self.shown)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
|
tree.diff_children(&mut [&mut self.matched, &mut self.shown]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Msg: 'a> From<MatchSize<'a, Msg>> for cosmic::Element<'a, Msg> {
|
||||||
|
fn from(widget: MatchSize<'a, Msg>) -> Self {
|
||||||
|
cosmic::Element::new(widget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,8 @@ mod toplevels;
|
||||||
pub use toplevels::toplevels;
|
pub use toplevels::toplevels;
|
||||||
mod visibility_wrapper;
|
mod visibility_wrapper;
|
||||||
pub use visibility_wrapper::visibility_wrapper;
|
pub use visibility_wrapper::visibility_wrapper;
|
||||||
|
mod match_size;
|
||||||
|
pub use match_size::match_size;
|
||||||
|
|
||||||
// Widget for debugging
|
// Widget for debugging
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue