Allow dragging toplevel
This commit is contained in:
parent
8722fe574a
commit
e5aca0a6b5
4 changed files with 114 additions and 28 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -876,6 +876,7 @@ dependencies = [
|
||||||
"gbm",
|
"gbm",
|
||||||
"libcosmic",
|
"libcosmic",
|
||||||
"memmap2 0.9.0",
|
"memmap2 0.9.0",
|
||||||
|
"once_cell",
|
||||||
"tokio",
|
"tokio",
|
||||||
"wayland-protocols 0.31.0",
|
"wayland-protocols 0.31.0",
|
||||||
"zbus",
|
"zbus",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ memmap2 = "0.9.0"
|
||||||
tokio = "1.23.0"
|
tokio = "1.23.0"
|
||||||
wayland-protocols = "0.31.0"
|
wayland-protocols = "0.31.0"
|
||||||
zbus = { version = "3.7.0", default-features = false, features = ["tokio"] }
|
zbus = { version = "3.7.0", default-features = false, features = ["tokio"] }
|
||||||
|
once_cell = "1.18.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Not usable at opt-level 0, at least with software renderer
|
# Not usable at opt-level 0, at least with software renderer
|
||||||
|
|
|
||||||
85
src/main.rs
85
src/main.rs
|
|
@ -9,6 +9,7 @@ use cctk::{
|
||||||
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
|
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
|
||||||
toplevel_info::ToplevelInfo,
|
toplevel_info::ToplevelInfo,
|
||||||
wayland_client::{
|
wayland_client::{
|
||||||
|
backend::ObjectId,
|
||||||
protocol::{wl_data_device_manager::DndAction, wl_output, wl_seat},
|
protocol::{wl_data_device_manager::DndAction, wl_output, wl_seat},
|
||||||
Connection, Proxy, WEnum,
|
Connection, Proxy, WEnum,
|
||||||
},
|
},
|
||||||
|
|
@ -36,6 +37,7 @@ use cosmic::{
|
||||||
iced_sctk::commands::layer_surface::{destroy_layer_surface, get_layer_surface},
|
iced_sctk::commands::layer_surface::{destroy_layer_surface, get_layer_surface},
|
||||||
};
|
};
|
||||||
use cosmic_config::ConfigGet;
|
use cosmic_config::ConfigGet;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
mem,
|
mem,
|
||||||
|
|
@ -44,7 +46,13 @@ use std::{
|
||||||
mod view;
|
mod view;
|
||||||
mod wayland;
|
mod wayland;
|
||||||
|
|
||||||
static WORKSPACE_MIME: &str = "text/x.cosmic-workspace-id";
|
// Include `pid` in mime. Want to drag between our surfaces, but not another
|
||||||
|
// process, if we use Wayland object ids.
|
||||||
|
static WORKSPACE_MIME: Lazy<String> =
|
||||||
|
Lazy::new(|| format!("text/x.cosmic-workspace-id-{}", std::process::id()));
|
||||||
|
|
||||||
|
static TOPLEVEL_MIME: Lazy<String> =
|
||||||
|
Lazy::new(|| format!("text/x.cosmic-toplevel-id-{}", std::process::id()));
|
||||||
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
|
@ -69,11 +77,26 @@ impl CosmicFlags for Args {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WlDndId {
|
||||||
|
id: ObjectId,
|
||||||
|
mime_type: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataFromMimeType for WlDndId {
|
||||||
|
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
|
||||||
|
if mime_type == self.mime_type {
|
||||||
|
Some(self.id.protocol_id().to_string().into_bytes())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct WorkspaceDndId(String);
|
struct WorkspaceDndId(String);
|
||||||
|
|
||||||
impl DataFromMimeType for WorkspaceDndId {
|
impl DataFromMimeType for WorkspaceDndId {
|
||||||
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
|
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
|
||||||
if mime_type == WORKSPACE_MIME {
|
if mime_type == *WORKSPACE_MIME {
|
||||||
Some(self.0.as_bytes().to_vec())
|
Some(self.0.as_bytes().to_vec())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -135,6 +158,19 @@ enum DragSurface {
|
||||||
name: String,
|
name: String,
|
||||||
output: wl_output::WlOutput,
|
output: wl_output::WlOutput,
|
||||||
},
|
},
|
||||||
|
Toplevel {
|
||||||
|
handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
|
||||||
|
output: wl_output::WlOutput,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DragSurface {
|
||||||
|
fn output(&self) -> &wl_output::WlOutput {
|
||||||
|
match self {
|
||||||
|
Self::Workspace { output, .. } => output,
|
||||||
|
Self::Toplevel { output, .. } => output,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Conf {
|
struct Conf {
|
||||||
|
|
@ -473,23 +509,38 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::StartDrag(size, drag_surface) => {
|
Msg::StartDrag(size, drag_surface) => {
|
||||||
match &drag_surface {
|
let output = drag_surface.output();
|
||||||
DragSurface::Workspace { output, name: _ } => {
|
let id = self.next_surface_id();
|
||||||
let id = self.next_surface_id();
|
if let Some((parent_id, _)) = self
|
||||||
if let Some((parent_id, _)) = self
|
.layer_surfaces
|
||||||
.layer_surfaces
|
.iter()
|
||||||
.iter()
|
.find(|(_, x)| &x.output == output)
|
||||||
.find(|(_, x)| &x.output == output)
|
{
|
||||||
{
|
match &drag_surface {
|
||||||
|
DragSurface::Workspace { output, name: _ } => {
|
||||||
self.drag_surface = Some((id, drag_surface, size));
|
self.drag_surface = Some((id, drag_surface, size));
|
||||||
return start_drag(
|
return start_drag(
|
||||||
vec![WORKSPACE_MIME.to_string()],
|
vec![WORKSPACE_MIME.to_string()],
|
||||||
DndAction::Move,
|
DndAction::Move,
|
||||||
*parent_id,
|
*parent_id,
|
||||||
Some(DndIcon::Custom(id)), // TODO store
|
Some(DndIcon::Custom(id)),
|
||||||
Box::new(WorkspaceDndId(String::new())),
|
Box::new(WorkspaceDndId(String::new())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
DragSurface::Toplevel { handle, .. } => {
|
||||||
|
let handle = handle.clone();
|
||||||
|
self.drag_surface = Some((id, drag_surface, size));
|
||||||
|
return start_drag(
|
||||||
|
vec![TOPLEVEL_MIME.to_string()],
|
||||||
|
DndAction::Move,
|
||||||
|
*parent_id,
|
||||||
|
Some(DndIcon::Custom(id)),
|
||||||
|
Box::new(WlDndId {
|
||||||
|
id: handle.id(),
|
||||||
|
mime_type: &*TOPLEVEL_MIME,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -500,7 +551,7 @@ impl Application for App {
|
||||||
println!("Workspace enter: {:?}", (action, &mimes));
|
println!("Workspace enter: {:?}", (action, &mimes));
|
||||||
// XXX
|
// XXX
|
||||||
// if mimes.iter().any(|x| x == WORKSPACE_MIME) && action == DndAction::Move {
|
// if mimes.iter().any(|x| x == WORKSPACE_MIME) && action == DndAction::Move {
|
||||||
if mimes.iter().any(|x| x == WORKSPACE_MIME) {
|
if mimes.iter().any(|x| x == &*WORKSPACE_MIME) {
|
||||||
return Command::batch(vec![
|
return Command::batch(vec![
|
||||||
set_actions(DndAction::Move, DndAction::Move),
|
set_actions(DndAction::Move, DndAction::Move),
|
||||||
accept_mime_type(Some(WORKSPACE_MIME.to_string())),
|
accept_mime_type(Some(WORKSPACE_MIME.to_string())),
|
||||||
|
|
@ -576,6 +627,16 @@ impl Application for App {
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DragSurface::Toplevel { handle, .. } => {
|
||||||
|
if let Some(toplevel) = self.toplevels.iter().find(|x| &x.handle == handle)
|
||||||
|
{
|
||||||
|
let item = view::toplevel_preview(toplevel);
|
||||||
|
return widget::container(item)
|
||||||
|
.height(iced::Length::Fixed(size.height))
|
||||||
|
.width(iced::Length::Fixed(size.width))
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,19 @@ pub(crate) fn layer_surface<'a>(
|
||||||
layout,
|
layout,
|
||||||
app.conf.workspace_config.workspace_amount,
|
app.conf.workspace_config.workspace_amount,
|
||||||
);
|
);
|
||||||
let toplevels = toplevel_previews(app.toplevels.iter().filter(|i| {
|
let toplevels = toplevel_previews(
|
||||||
if !i.info.output.contains(&surface.output) {
|
app.toplevels.iter().filter(|i| {
|
||||||
return false;
|
if !i.info.output.contains(&surface.output) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
i.info.workspace.iter().any(|workspace| {
|
i.info.workspace.iter().any(|workspace| {
|
||||||
app.workspace_for_handle(workspace)
|
app.workspace_for_handle(workspace)
|
||||||
.map_or(false, |x| x.is_active)
|
.map_or(false, |x| x.is_active)
|
||||||
})
|
})
|
||||||
}));
|
}),
|
||||||
|
&surface.output,
|
||||||
|
);
|
||||||
match layout {
|
match layout {
|
||||||
WorkspaceLayout::Vertical => widget::cosmic_container::container(
|
WorkspaceLayout::Vertical => widget::cosmic_container::container(
|
||||||
row![sidebar, toplevels]
|
row![sidebar, toplevels]
|
||||||
|
|
@ -126,7 +129,7 @@ fn workspaces_sidebar<'a>(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element<Msg> {
|
pub(crate) fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element<Msg> {
|
||||||
column![
|
column![
|
||||||
close_button(Msg::CloseToplevel(toplevel.handle.clone())),
|
close_button(Msg::CloseToplevel(toplevel.handle.clone())),
|
||||||
widget::button(capture_image(toplevel.img.as_ref()))
|
widget::button(capture_image(toplevel.img.as_ref()))
|
||||||
|
|
@ -145,15 +148,35 @@ fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element<Msg> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toplevel_previews_entry<'a>(
|
||||||
|
toplevel: &'a Toplevel,
|
||||||
|
output: &'a wl_output::WlOutput,
|
||||||
|
) -> cosmic::Element<'a, Msg> {
|
||||||
|
iced::widget::dnd_source(toplevel_preview(toplevel))
|
||||||
|
.on_drag(|size| {
|
||||||
|
Msg::StartDrag(
|
||||||
|
size,
|
||||||
|
DragSurface::Toplevel {
|
||||||
|
handle: toplevel.handle.clone(),
|
||||||
|
output: output.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn toplevel_previews<'a>(
|
fn toplevel_previews<'a>(
|
||||||
toplevels: impl Iterator<Item = &'a Toplevel>,
|
toplevels: impl Iterator<Item = &'a Toplevel>,
|
||||||
|
output: &'a wl_output::WlOutput,
|
||||||
) -> cosmic::Element<'a, Msg> {
|
) -> cosmic::Element<'a, Msg> {
|
||||||
row(toplevels.map(toplevel_preview).collect())
|
row(toplevels
|
||||||
.width(iced::Length::FillPortion(4))
|
.map(|t| toplevel_previews_entry(t, output))
|
||||||
.height(iced::Length::Fill)
|
.collect())
|
||||||
.spacing(16)
|
.width(iced::Length::FillPortion(4))
|
||||||
.align_items(iced::Alignment::Center)
|
.height(iced::Length::Fill)
|
||||||
.into()
|
.spacing(16)
|
||||||
|
.align_items(iced::Alignment::Center)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capture_image(image: Option<&CaptureImage>) -> cosmic::Element<'_, Msg> {
|
fn capture_image(image: Option<&CaptureImage>) -> cosmic::Element<'_, Msg> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue