Order displays based on logical position
This commit is contained in:
parent
f6dbb37a02
commit
1ba6c8a026
4 changed files with 228 additions and 180 deletions
|
|
@ -9,104 +9,98 @@ class Bar extends ConsumerWidget {
|
||||||
const Bar({super.key});
|
const Bar({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => Center(
|
Widget build(BuildContext context, WidgetRef ref) => Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
child: Stack(
|
||||||
child: Stack(
|
children:
|
||||||
children:
|
[
|
||||||
[
|
ref
|
||||||
ref
|
.watch(WorkspacesController.provider)
|
||||||
.watch(WorkspacesController.provider)
|
.when(
|
||||||
.when(
|
error: (error, stackTrace) => [Text(error.toString())],
|
||||||
error: (error, stackTrace) => [Text(error.toString())],
|
loading: () => [SizedBox.shrink()],
|
||||||
loading: () => [SizedBox.shrink()],
|
data: (value) => value
|
||||||
data: (value) => value
|
|
||||||
.map(
|
|
||||||
(group) => Row(
|
|
||||||
children: group
|
|
||||||
.map(
|
|
||||||
(workspace) => IconButton(
|
|
||||||
onPressed: () => workspace.activate(),
|
|
||||||
icon: Icon(
|
|
||||||
workspace.activated
|
|
||||||
? Icons.circle
|
|
||||||
: Icons.circle_outlined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
[
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: Text(
|
|
||||||
DateFormat.Hm().format(
|
|
||||||
ref
|
|
||||||
.watch(TimeController.provider)
|
|
||||||
.when(
|
|
||||||
data: (time) => time,
|
|
||||||
loading: DateTime.now,
|
|
||||||
error: (_, _) => DateTime.now(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
IconButton(onPressed: () {}, icon: Icon(Icons.wifi)),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: Icon(Icons.bluetooth),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: Icon(Icons.volume_off),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
.mapIndexed(
|
|
||||||
(index, children) => Align(
|
|
||||||
alignment: [
|
|
||||||
Alignment.centerLeft,
|
|
||||||
Alignment.center,
|
|
||||||
Alignment.centerRight,
|
|
||||||
][index],
|
|
||||||
child: Row(
|
|
||||||
spacing: 8,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: children
|
|
||||||
.map(
|
.map(
|
||||||
(child) => Padding(
|
(group) => Row(
|
||||||
padding: EdgeInsetsGeometry.directional(
|
children: group
|
||||||
bottom: 6,
|
.map(
|
||||||
),
|
(workspace) => IconButton(
|
||||||
child: Container(
|
onPressed: () => workspace.activate(),
|
||||||
height: 42,
|
icon: Icon(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
workspace.activated
|
||||||
decoration: BoxDecoration(
|
? Icons.circle
|
||||||
color: Theme.of(
|
: Icons.circle_outlined,
|
||||||
context,
|
),
|
||||||
).colorScheme.surfaceContainerLow,
|
),
|
||||||
borderRadius: BorderRadius.circular(999),
|
)
|
||||||
),
|
.toList(),
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: Text(
|
||||||
|
DateFormat.Hm().format(
|
||||||
|
ref
|
||||||
|
.watch(TimeController.provider)
|
||||||
|
.when(
|
||||||
|
data: (time) => time,
|
||||||
|
loading: DateTime.now,
|
||||||
|
error: (_, _) => DateTime.now(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
.toList(),
|
|
||||||
),
|
[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(onPressed: () {}, icon: Icon(Icons.wifi)),
|
||||||
|
IconButton(onPressed: () {}, icon: Icon(Icons.bluetooth)),
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: Icon(Icons.volume_off),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
.mapIndexed(
|
||||||
|
(index, children) => Align(
|
||||||
|
alignment: [
|
||||||
|
Alignment.centerLeft,
|
||||||
|
Alignment.center,
|
||||||
|
Alignment.centerRight,
|
||||||
|
][index],
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: children
|
||||||
|
.map(
|
||||||
|
(child) => Padding(
|
||||||
|
padding: EdgeInsetsGeometry.directional(bottom: 6),
|
||||||
|
child: Container(
|
||||||
|
height: 42,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerLow,
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
wayland-client = "0.31"
|
wayland-client = "0.31"
|
||||||
wayland-protocols = { version = "0.32.12", features = ["client", "staging"] }
|
wayland-protocols = { version = "0.32.12", features = ["client", "staging", "unstable"] }
|
||||||
|
|
||||||
calloop = "0.14.4"
|
calloop = "0.14.4"
|
||||||
calloop-wayland-source = "0.4.1"
|
calloop-wayland-source = "0.4.1"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use crate::{frb_generated::StreamSink, internal::wayland::AppState};
|
use crate::{frb_generated::StreamSink, internal::wayland::AppState};
|
||||||
use std::{collections::HashMap, sync::{Mutex, OnceLock}};
|
|
||||||
use serde_json::Result;
|
use serde_json::Result;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Mutex, OnceLock},
|
||||||
|
};
|
||||||
|
|
||||||
use calloop::{
|
use calloop::{
|
||||||
|
channel::{channel, Channel, Sender},
|
||||||
EventLoop,
|
EventLoop,
|
||||||
channel::{Channel, Sender, channel},
|
|
||||||
};
|
};
|
||||||
use calloop_wayland_source::WaylandSource;
|
use calloop_wayland_source::WaylandSource;
|
||||||
|
|
||||||
|
|
@ -25,8 +28,8 @@ pub struct Workspace {
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
pub fn activate(&self) {
|
pub fn activate(&self) {
|
||||||
if let Some(tx) = controller().lock().unwrap().as_ref() {
|
if let Some(tx) = controller().lock().unwrap().as_ref() {
|
||||||
let _ = tx.send(self.id);
|
let _ = tx.send(self.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,10 +42,11 @@ pub fn listen_workspaces(sink: StreamSink<Vec<Vec<Workspace>>>) -> Result<()> {
|
||||||
let qh = event_queue.handle();
|
let qh = event_queue.handle();
|
||||||
|
|
||||||
let mut state = AppState {
|
let mut state = AppState {
|
||||||
outputs: HashMap::new(),
|
outputs: HashMap::new(),
|
||||||
workspaces: HashMap::new(),
|
workspaces: HashMap::new(),
|
||||||
workspace_groups: HashMap::new(),
|
workspace_groups: HashMap::new(),
|
||||||
workspace_handles: HashMap::new(),
|
workspace_handles: HashMap::new(),
|
||||||
|
xdg_output_manager: None,
|
||||||
sink,
|
sink,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -69,4 +73,4 @@ pub fn listen_workspaces(sink: StreamSink<Vec<Vec<Workspace>>>) -> Result<()> {
|
||||||
while let Ok(_) = event_loop.dispatch(None, &mut state) {}
|
while let Ok(_) = event_loop.dispatch(None, &mut state) {}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,16 @@ use crate::{frb_generated::StreamSink, workspace_api::Workspace};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
self, event_created_child, protocol::{wl_registry, wl_output}, Connection, Dispatch, Proxy, QueueHandle,
|
self, event_created_child,
|
||||||
|
protocol::{wl_output, wl_registry},
|
||||||
|
Connection, Dispatch, Proxy, QueueHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use wayland_protocols::ext::workspace::v1::client::{
|
use wayland_protocols::{
|
||||||
ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1,
|
ext::workspace::v1::client::{
|
||||||
|
ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1,
|
||||||
|
},
|
||||||
|
xdg::xdg_output::zv1::client::{zxdg_output_manager_v1, zxdg_output_v1},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct WorkspaceHandles {
|
pub struct WorkspaceHandles {
|
||||||
|
|
@ -29,34 +34,34 @@ pub struct AppState {
|
||||||
pub workspace_groups: HashMap<u32, WorkspaceGroup>,
|
pub workspace_groups: HashMap<u32, WorkspaceGroup>,
|
||||||
pub workspace_handles: HashMap<u32, WorkspaceHandles>,
|
pub workspace_handles: HashMap<u32, WorkspaceHandles>,
|
||||||
pub sink: StreamSink<Vec<Vec<Workspace>>>,
|
pub sink: StreamSink<Vec<Vec<Workspace>>>,
|
||||||
|
pub xdg_output_manager: Option<zxdg_output_manager_v1::ZxdgOutputManagerV1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn emit(&self) {
|
fn emit(&self) {
|
||||||
let mut groups: Vec<_> = self.workspace_groups.values().collect();
|
let mut groups: Vec<_> = self.workspace_groups.values().collect();
|
||||||
|
|
||||||
groups.sort_by_key(|group| {
|
groups.sort_by_key(|group| {
|
||||||
group.output_id.as_ref().and_then(|o| {
|
group
|
||||||
self.outputs.get(o).map(|info| info.coords.0)
|
.output_id
|
||||||
})
|
.as_ref()
|
||||||
});
|
.and_then(|o| self.outputs.get(o).map(|info| info.coords.0))
|
||||||
|
});
|
||||||
|
|
||||||
let result: Vec<Vec<Workspace>> = groups
|
let result: Vec<Vec<Workspace>> = groups
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|group| {
|
.map(|group| {
|
||||||
let mut workspaces: Vec<Workspace> = group
|
let mut workspaces: Vec<Workspace> = group
|
||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| self.workspaces.get(id))
|
.filter_map(|id| self.workspaces.get(id))
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
workspaces.sort_by_key(|ws|
|
workspaces.sort_by_key(|ws| ws.coords.as_ref().map(|coords| coords.0));
|
||||||
ws.coords.as_ref().map(|coords| coords.0)
|
workspaces
|
||||||
);
|
})
|
||||||
workspaces
|
.collect();
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
self.sink.add(result).expect("Updating stream failed");
|
self.sink.add(result).expect("Updating stream failed");
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +69,7 @@ impl AppState {
|
||||||
|
|
||||||
impl Dispatch<wl_registry::WlRegistry, ()> for AppState {
|
impl Dispatch<wl_registry::WlRegistry, ()> for AppState {
|
||||||
fn event(
|
fn event(
|
||||||
_state: &mut Self,
|
state: &mut Self,
|
||||||
registry: &wl_registry::WlRegistry,
|
registry: &wl_registry::WlRegistry,
|
||||||
event: wl_registry::Event,
|
event: wl_registry::Event,
|
||||||
_data: &(),
|
_data: &(),
|
||||||
|
|
@ -77,27 +82,34 @@ impl Dispatch<wl_registry::WlRegistry, ()> for AppState {
|
||||||
version,
|
version,
|
||||||
} = event
|
} = event
|
||||||
{
|
{
|
||||||
match interface.as_str() {
|
match interface.as_str() {
|
||||||
"ext_workspace_manager_v1" => {
|
"ext_workspace_manager_v1" => {
|
||||||
registry.bind::<ext_workspace_manager_v1::ExtWorkspaceManagerV1, (), AppState>(
|
registry.bind::<ext_workspace_manager_v1::ExtWorkspaceManagerV1, (), AppState>(
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
qh,
|
qh,
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
"wl_output" => {
|
"zxdg_output_manager_v1" => {
|
||||||
registry.bind::<wl_output::WlOutput, (), AppState>(
|
let manager: zxdg_output_manager_v1::ZxdgOutputManagerV1 =
|
||||||
name,
|
registry.bind::<zxdg_output_manager_v1::ZxdgOutputManagerV1, (), AppState>(
|
||||||
version,
|
name,
|
||||||
qh,
|
version,
|
||||||
(),
|
qh,
|
||||||
);
|
(),
|
||||||
}
|
);
|
||||||
|
|
||||||
_ => {}
|
state.xdg_output_manager = Some(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"wl_output" => {
|
||||||
|
registry.bind::<wl_output::WlOutput, (), AppState>(name, version, qh, ());
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,18 +129,18 @@ impl Dispatch<ext_workspace_manager_v1::ExtWorkspaceManagerV1, ()> for AppState
|
||||||
workspace.id().protocol_id(),
|
workspace.id().protocol_id(),
|
||||||
Workspace {
|
Workspace {
|
||||||
activated: false,
|
activated: false,
|
||||||
id: workspace.id().protocol_id(),
|
id: workspace.id().protocol_id(),
|
||||||
coords: None,
|
coords: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
state.workspace_handles.insert(
|
state.workspace_handles.insert(
|
||||||
workspace.id().protocol_id(),
|
workspace.id().protocol_id(),
|
||||||
WorkspaceHandles {
|
WorkspaceHandles {
|
||||||
handle: workspace,
|
handle: workspace,
|
||||||
manager_handle: proxy.clone(),
|
manager_handle: proxy.clone(),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +219,7 @@ impl Dispatch<ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, ()> for
|
||||||
event: ext_workspace_group_handle_v1::Event,
|
event: ext_workspace_group_handle_v1::Event,
|
||||||
_data: &(),
|
_data: &(),
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
let group_id = proxy.id();
|
let group_id = proxy.id();
|
||||||
|
|
||||||
|
|
@ -215,26 +227,30 @@ impl Dispatch<ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, ()> for
|
||||||
ext_workspace_group_handle_v1::Event::WorkspaceEnter { workspace } => {
|
ext_workspace_group_handle_v1::Event::WorkspaceEnter { workspace } => {
|
||||||
state
|
state
|
||||||
.workspace_groups
|
.workspace_groups
|
||||||
.entry(group_id.protocol_id())
|
.entry(group_id.protocol_id())
|
||||||
.or_insert_with(|| WorkspaceGroup {
|
.or_insert_with(|| WorkspaceGroup {
|
||||||
output_id: None,
|
output_id: None,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
})
|
})
|
||||||
.children
|
.children
|
||||||
.push(workspace.id().protocol_id());
|
.push(workspace.id().protocol_id());
|
||||||
state.emit();
|
state.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
ext_workspace_group_handle_v1::Event::OutputEnter { output } => {
|
ext_workspace_group_handle_v1::Event::OutputEnter { output } => {
|
||||||
state
|
if let Some(manager) = &state.xdg_output_manager {
|
||||||
.workspace_groups
|
let xdg_output = manager.get_xdg_output(&output, qh, ());
|
||||||
.entry(group_id.protocol_id())
|
state
|
||||||
.or_insert_with(|| WorkspaceGroup {
|
.workspace_groups
|
||||||
output_id: None,
|
.entry(group_id.protocol_id())
|
||||||
children: Vec::new(),
|
.or_insert_with(|| WorkspaceGroup {
|
||||||
})
|
output_id: None,
|
||||||
.output_id = Some(output.id().protocol_id());
|
children: Vec::new(),
|
||||||
state.emit();
|
})
|
||||||
|
.output_id = Some(xdg_output.id().protocol_id());
|
||||||
|
|
||||||
|
state.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext_workspace_group_handle_v1::Event::WorkspaceLeave { workspace } => {
|
ext_workspace_group_handle_v1::Event::WorkspaceLeave { workspace } => {
|
||||||
|
|
@ -247,25 +263,59 @@ impl Dispatch<ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, ()> for
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatch<wl_output::WlOutput, ()> for AppState {
|
impl Dispatch<zxdg_output_manager_v1::ZxdgOutputManagerV1, ()> for AppState {
|
||||||
|
fn event(
|
||||||
|
_state: &mut Self,
|
||||||
|
_proxy: &zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||||
|
_event: <zxdg_output_manager_v1::ZxdgOutputManagerV1 as Proxy>::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_qhandle: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
// Do nothing, for now
|
||||||
|
}
|
||||||
|
|
||||||
|
event_created_child!(
|
||||||
|
AppState,
|
||||||
|
zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||||
|
[
|
||||||
|
zxdg_output_manager_v1::REQ_GET_XDG_OUTPUT_OPCODE => (
|
||||||
|
zxdg_output_v1::ZxdgOutputV1,
|
||||||
|
()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<zxdg_output_v1::ZxdgOutputV1, ()> for AppState {
|
||||||
fn event(
|
fn event(
|
||||||
state: &mut Self,
|
state: &mut Self,
|
||||||
proxy: &wl_output::WlOutput,
|
proxy: &zxdg_output_v1::ZxdgOutputV1,
|
||||||
event: wl_output::Event,
|
event: zxdg_output_v1::Event,
|
||||||
_data: &(),
|
_data: &(),
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
wl_output::Event::Geometry {
|
zxdg_output_v1::Event::LogicalPosition { x, y } => {
|
||||||
x,
|
state
|
||||||
y,
|
.outputs
|
||||||
..
|
.insert(proxy.id().protocol_id(), Output { coords: (x, y) });
|
||||||
} => {
|
|
||||||
state.outputs.insert(proxy.id().protocol_id(), Output { coords: ( x, y ) });
|
|
||||||
state.emit();
|
state.emit();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Dispatch<wl_output::WlOutput, ()> for AppState {
|
||||||
|
fn event(
|
||||||
|
_state: &mut Self,
|
||||||
|
_proxy: &wl_output::WlOutput,
|
||||||
|
_event: wl_output::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
// Do nothing, just bind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue