control-center: add window pane
This commit is contained in:
parent
aefd1fbbdb
commit
ca6fc54246
9 changed files with 611 additions and 11 deletions
|
|
@ -9,6 +9,7 @@ use {
|
|||
cc_input::InputPane,
|
||||
cc_look_and_feel::LookAndFeelPane,
|
||||
cc_outputs::OutputsPane,
|
||||
cc_window::{WindowPane, WindowSearchPane},
|
||||
cc_xwayland::XwaylandPane,
|
||||
},
|
||||
egui_adapter::egui_platform::{
|
||||
|
|
@ -18,8 +19,8 @@ use {
|
|||
macros::Bitflag,
|
||||
state::State,
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, copyhashmap::CopyHashMap, numcell::NumCell,
|
||||
static_text::StaticText,
|
||||
asyncevent::AsyncEvent, copyhashmap::CopyHashMap,
|
||||
event_listener::LazyEventSourceListener, numcell::NumCell, static_text::StaticText,
|
||||
},
|
||||
},
|
||||
egui::{
|
||||
|
|
@ -50,6 +51,7 @@ mod cc_input;
|
|||
mod cc_look_and_feel;
|
||||
mod cc_outputs;
|
||||
mod cc_sidebar;
|
||||
mod cc_window;
|
||||
mod cc_xwayland;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -141,6 +143,8 @@ enum PaneType {
|
|||
LookAndFeel(LookAndFeelPane),
|
||||
Clients(ClientsPane),
|
||||
Client(ClientPane),
|
||||
WindowSearch(WindowSearchPane),
|
||||
Window(WindowPane),
|
||||
}
|
||||
|
||||
struct CcBehavior<'a> {
|
||||
|
|
@ -168,6 +172,8 @@ impl Pane {
|
|||
PaneType::LookAndFeel(v) => v.title(res),
|
||||
PaneType::Clients(v) => v.title(res),
|
||||
PaneType::Client(v) => v.title(res),
|
||||
PaneType::WindowSearch(v) => v.title(res),
|
||||
PaneType::Window(v) => v.title(res),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +189,8 @@ impl Pane {
|
|||
PaneType::LookAndFeel(p) => p.show(ui),
|
||||
PaneType::Clients(p) => p.show(behavior, ui),
|
||||
PaneType::Client(p) => p.show(behavior, ui),
|
||||
PaneType::WindowSearch(p) => p.show(behavior, ui),
|
||||
PaneType::Window(p) => p.show(behavior, ui),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -200,6 +208,8 @@ impl PaneType {
|
|||
PaneType::LookAndFeel(_) => CCI_LOOK_AND_FEEL,
|
||||
PaneType::Clients(_) => ControlCenterInterest::none(),
|
||||
PaneType::Client(_) => ControlCenterInterest::none(),
|
||||
PaneType::WindowSearch(_) => ControlCenterInterest::none(),
|
||||
PaneType::Window(_) => ControlCenterInterest::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -663,3 +673,9 @@ impl Drop for GridRow<'_> {
|
|||
self.end_row();
|
||||
}
|
||||
}
|
||||
|
||||
impl LazyEventSourceListener for ControlCenterInner {
|
||||
fn triggered(self: Rc<Self>) {
|
||||
self.window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,28 @@ use {
|
|||
control_center::{
|
||||
CcBehavior, ControlCenterInner, PaneType,
|
||||
cc_criterion::{CcCriterion, CritImpl, CritRegex},
|
||||
cc_window::show_window_collapsible,
|
||||
grid, icon_label, label, read_only_bool,
|
||||
},
|
||||
criteria::{CritMgrExt, CritUpstreamNode, crit_leaf::CritLeafMatcher},
|
||||
egui_adapter::egui_platform::icons::ICON_OPEN_IN_NEW,
|
||||
state::State,
|
||||
utils::{copyhashmap::CopyHashMap, static_text::StaticText},
|
||||
tree::ToplevelData,
|
||||
utils::{
|
||||
copyhashmap::CopyHashMap, static_text::StaticText,
|
||||
toplevel_identifier::ToplevelIdentifier,
|
||||
},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
egui::{
|
||||
CollapsingHeader, DragValue, Sense, TextFormat, Ui, Widget, cache::CacheTrait,
|
||||
text::LayoutJob,
|
||||
},
|
||||
egui::{CollapsingHeader, DragValue, Sense, TextFormat, Ui, Widget, text::LayoutJob},
|
||||
linearize::Linearize,
|
||||
std::rc::{Rc, Weak},
|
||||
std::{
|
||||
any::Any,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
pub enum ClientCrit {
|
||||
|
|
@ -318,7 +330,7 @@ pub fn show_client_collapsible(behavior: &mut CcBehavior, ui: &mut Ui, client: &
|
|||
});
|
||||
}
|
||||
|
||||
pub fn show_client(_behavior: &mut CcBehavior<'_>, ui: &mut Ui, client: &Client) {
|
||||
pub fn show_client(behavior: &mut CcBehavior<'_>, ui: &mut Ui, client: &Client) {
|
||||
grid(ui, ("client", client.id), |ui| {
|
||||
label(ui, "ID", client.id.to_string());
|
||||
label(ui, "PID", client.pid_info.pid.to_string());
|
||||
|
|
@ -359,4 +371,93 @@ pub fn show_client(_behavior: &mut CcBehavior<'_>, ui: &mut Ui, client: &Client)
|
|||
}
|
||||
});
|
||||
});
|
||||
ui.collapsing("Windows", |ui| {
|
||||
let matcher = ui.memory_mut(|m| {
|
||||
m.caches
|
||||
.cache::<ClientWindowMatchersCache>()
|
||||
.get(behavior.cc, client.id)
|
||||
.clone()
|
||||
});
|
||||
let mut windows: Vec<_> = matcher.windows.lock().keys().copied().collect();
|
||||
windows.sort();
|
||||
for id in windows {
|
||||
let Some(window) = client.state.toplevels.get(&id).and_then(|v| v.upgrade()) else {
|
||||
continue;
|
||||
};
|
||||
show_window_collapsible(behavior, ui, &window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClientWindowMatchersCache {
|
||||
generation: u64,
|
||||
matchers: AHashMap<ClientId, CachedWindowMatcher>,
|
||||
}
|
||||
|
||||
struct CachedWindowMatcher {
|
||||
generation: u64,
|
||||
_matcher: Rc<CritLeafMatcher<ToplevelData>>,
|
||||
matchers: Rc<WindowMatchers>,
|
||||
}
|
||||
|
||||
struct WindowMatchers {
|
||||
cc: Weak<ControlCenterInner>,
|
||||
windows: CopyHashMap<ToplevelIdentifier, ()>,
|
||||
}
|
||||
|
||||
impl ClientWindowMatchersCache {
|
||||
fn get(&mut self, cc: &Rc<ControlCenterInner>, id: ClientId) -> &Rc<WindowMatchers> {
|
||||
let res = self.matchers.entry(id).or_insert_with(|| {
|
||||
let state = &cc.state;
|
||||
let node = state.cl_matcher_manager.id(id);
|
||||
let node = state.tl_matcher_manager.client(state, &node);
|
||||
let matchers = Rc::new(WindowMatchers {
|
||||
cc: Rc::downgrade(&cc),
|
||||
windows: Default::default(),
|
||||
});
|
||||
let matchers2 = matchers.clone();
|
||||
let matcher = state.tl_matcher_manager.leaf(&node, move |id| {
|
||||
matchers2.windows.set(id, ());
|
||||
if let Some(cc) = matchers2.cc.upgrade() {
|
||||
cc.window.request_redraw();
|
||||
}
|
||||
let matchers2 = matchers2.clone();
|
||||
Box::new(move || {
|
||||
matchers2.windows.remove(&id);
|
||||
if let Some(cc) = matchers2.cc.upgrade() {
|
||||
cc.window.request_redraw();
|
||||
}
|
||||
})
|
||||
});
|
||||
let res = CachedWindowMatcher {
|
||||
generation: 0,
|
||||
_matcher: matcher,
|
||||
matchers,
|
||||
};
|
||||
state.cl_matcher_manager.rematch_all(state);
|
||||
state.tl_matcher_manager.rematch_all(state);
|
||||
res
|
||||
});
|
||||
res.generation = self.generation;
|
||||
&res.matchers
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for ClientWindowMatchersCache {}
|
||||
unsafe impl Send for ClientWindowMatchersCache {}
|
||||
|
||||
impl CacheTrait for ClientWindowMatchersCache {
|
||||
fn update(&mut self) {
|
||||
self.matchers.retain(|_, m| m.generation == self.generation);
|
||||
self.generation += 1;
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.matchers.len()
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn any(&self, mut any: impl FnMut(&T) -> bool) -> bool {
|
||||
self.any_(&mut any)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ enum PaneName {
|
|||
Input,
|
||||
LookAndFeel,
|
||||
Clients,
|
||||
WindowSearch,
|
||||
}
|
||||
|
||||
impl PaneName {
|
||||
|
|
@ -31,6 +32,7 @@ impl PaneName {
|
|||
PaneName::Input => "Input",
|
||||
PaneName::LookAndFeel => "Look and Feel",
|
||||
PaneName::Clients => "Clients",
|
||||
PaneName::WindowSearch => "Window Search",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +76,9 @@ impl ControlCenterInner {
|
|||
PaneType::LookAndFeel(self.create_look_and_feel_pane())
|
||||
}
|
||||
PaneName::Clients => PaneType::Clients(self.create_clients_pane()),
|
||||
PaneName::WindowSearch => {
|
||||
PaneType::WindowSearch(self.create_window_search_pane())
|
||||
}
|
||||
};
|
||||
self.open(tree, ty);
|
||||
ui.ctx().request_repaint();
|
||||
|
|
|
|||
481
src/control_center/cc_window.rs
Normal file
481
src/control_center/cc_window.rs
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
use {
|
||||
crate::{
|
||||
control_center::{
|
||||
CcBehavior, ControlCenterInner, PaneType,
|
||||
cc_clients::{ClientCrit, show_client_collapsible},
|
||||
cc_criterion::{CcCriterion, CritImpl, CritRegex},
|
||||
grid, icon_label, label, read_only_bool,
|
||||
},
|
||||
criteria::{CritMgrExt, CritUpstreamNode, crit_leaf::CritLeafMatcher},
|
||||
egui_adapter::egui_platform::icons::ICON_OPEN_IN_NEW,
|
||||
state::State,
|
||||
tree::{NodeId, ToplevelData, ToplevelNode, ToplevelType},
|
||||
utils::{
|
||||
copyhashmap::CopyHashMap,
|
||||
event_listener::{EventListener, LazyEventSourceListener},
|
||||
static_text::StaticText,
|
||||
toplevel_identifier::ToplevelIdentifier,
|
||||
},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
egui::{CollapsingHeader, Sense, TextFormat, Ui, Widget, cache::CacheTrait, text::LayoutJob},
|
||||
isnt::std_1::primitive::IsntStrExt,
|
||||
jay_config::window::{
|
||||
ContentType, GAME_CONTENT, NO_CONTENT_TYPE, PHOTO_CONTENT, VIDEO_CONTENT,
|
||||
},
|
||||
linearize::Linearize,
|
||||
std::{
|
||||
any::Any,
|
||||
mem,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
enum WindowClit {
|
||||
Client(CcCriterion<ClientCrit>),
|
||||
Title(CritRegex),
|
||||
AppId(CritRegex),
|
||||
Floating,
|
||||
Visible,
|
||||
Urgent,
|
||||
Fullscreen,
|
||||
Tag(CritRegex),
|
||||
XClass(CritRegex),
|
||||
XInstance(CritRegex),
|
||||
XRole(CritRegex),
|
||||
Workspace(CritRegex),
|
||||
ContentTypes(ContentType),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Linearize)]
|
||||
enum WindowCritTy {
|
||||
Client,
|
||||
Title,
|
||||
AppId,
|
||||
Floating,
|
||||
Visible,
|
||||
Urgent,
|
||||
Fullscreen,
|
||||
Tag,
|
||||
XClass,
|
||||
XInstance,
|
||||
XRole,
|
||||
Workspace,
|
||||
ContentTypes,
|
||||
}
|
||||
|
||||
impl Default for WindowClit {
|
||||
fn default() -> Self {
|
||||
WindowClit::Title(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticText for WindowCritTy {
|
||||
fn text(&self) -> &'static str {
|
||||
match self {
|
||||
WindowCritTy::Client => "Client",
|
||||
WindowCritTy::Title => "Title",
|
||||
WindowCritTy::AppId => "App ID",
|
||||
WindowCritTy::Floating => "Floating",
|
||||
WindowCritTy::Visible => "Visible",
|
||||
WindowCritTy::Urgent => "Urgent",
|
||||
WindowCritTy::Fullscreen => "Fullscreen",
|
||||
WindowCritTy::Tag => "Tag",
|
||||
WindowCritTy::XClass => "X Class",
|
||||
WindowCritTy::XInstance => "X Instance",
|
||||
WindowCritTy::XRole => "X Role",
|
||||
WindowCritTy::Workspace => "Workspace",
|
||||
WindowCritTy::ContentTypes => "Content Types",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CritImpl for WindowClit {
|
||||
type Type = WindowCritTy;
|
||||
type Target = ToplevelData;
|
||||
|
||||
fn ty(&self) -> Self::Type {
|
||||
macro_rules! map {
|
||||
($($n:ident,)*) => {
|
||||
match self {
|
||||
$(
|
||||
Self::$n { .. } => WindowCritTy::$n,
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
map! {
|
||||
Client,
|
||||
Title,
|
||||
AppId,
|
||||
Floating,
|
||||
Visible,
|
||||
Urgent,
|
||||
Fullscreen,
|
||||
Tag,
|
||||
XClass,
|
||||
XInstance,
|
||||
XRole,
|
||||
Workspace,
|
||||
ContentTypes,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ty(ty: Self::Type) -> Self {
|
||||
match ty {
|
||||
WindowCritTy::Client => Self::Client(Default::default()),
|
||||
WindowCritTy::Title => Self::Title(Default::default()),
|
||||
WindowCritTy::AppId => Self::AppId(Default::default()),
|
||||
WindowCritTy::Floating => Self::Floating,
|
||||
WindowCritTy::Visible => Self::Visible,
|
||||
WindowCritTy::Urgent => Self::Urgent,
|
||||
WindowCritTy::Fullscreen => Self::Fullscreen,
|
||||
WindowCritTy::Tag => Self::Tag(Default::default()),
|
||||
WindowCritTy::XClass => Self::XClass(Default::default()),
|
||||
WindowCritTy::XInstance => Self::XInstance(Default::default()),
|
||||
WindowCritTy::XRole => Self::XRole(Default::default()),
|
||||
WindowCritTy::Workspace => Self::Workspace(Default::default()),
|
||||
WindowCritTy::ContentTypes => {
|
||||
Self::ContentTypes(PHOTO_CONTENT | VIDEO_CONTENT | GAME_CONTENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut Ui) -> bool {
|
||||
match self {
|
||||
WindowClit::Client(v) => v.show(ui),
|
||||
WindowClit::Title(v) => v.show(ui),
|
||||
WindowClit::AppId(v) => v.show(ui),
|
||||
WindowClit::Floating => false,
|
||||
WindowClit::Visible => false,
|
||||
WindowClit::Urgent => false,
|
||||
WindowClit::Fullscreen => false,
|
||||
WindowClit::Tag(v) => v.show(ui),
|
||||
WindowClit::XClass(v) => v.show(ui),
|
||||
WindowClit::XInstance(v) => v.show(ui),
|
||||
WindowClit::XRole(v) => v.show(ui),
|
||||
WindowClit::Workspace(v) => v.show(ui),
|
||||
WindowClit::ContentTypes(v) => show_content_types(ui, v),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_crit(&self, state: &Rc<State>) -> Option<Rc<dyn CritUpstreamNode<Self::Target>>> {
|
||||
let m = &state.tl_matcher_manager;
|
||||
let res = match self {
|
||||
WindowClit::Client(v) => m.client(state, &v.to_crit(state)?),
|
||||
WindowClit::Title(v) => m.title(v.to_crit()?),
|
||||
WindowClit::AppId(v) => m.app_id(v.to_crit()?),
|
||||
WindowClit::Floating => m.floating(),
|
||||
WindowClit::Visible => m.visible(),
|
||||
WindowClit::Urgent => m.urgent(),
|
||||
WindowClit::Fullscreen => m.fullscreen(),
|
||||
WindowClit::Tag(v) => m.tag(v.to_crit()?),
|
||||
WindowClit::XClass(v) => m.class(v.to_crit()?),
|
||||
WindowClit::XInstance(v) => m.instance(v.to_crit()?),
|
||||
WindowClit::XRole(v) => m.role(v.to_crit()?),
|
||||
WindowClit::Workspace(v) => m.workspace(v.to_crit()?),
|
||||
WindowClit::ContentTypes(v) => m.content_type(*v),
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn not(
|
||||
state: &State,
|
||||
upstream: &Rc<dyn CritUpstreamNode<Self::Target>>,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
state.tl_matcher_manager.not(upstream)
|
||||
}
|
||||
|
||||
fn list(
|
||||
state: &State,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
all: bool,
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
state.tl_matcher_manager.list(upstream, all)
|
||||
}
|
||||
|
||||
fn exactly(
|
||||
state: &State,
|
||||
n: usize,
|
||||
upstream: &[Rc<dyn CritUpstreamNode<Self::Target>>],
|
||||
) -> Rc<dyn CritUpstreamNode<Self::Target>> {
|
||||
state.tl_matcher_manager.exactly(upstream, n)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowSearchPane {
|
||||
state: Rc<State>,
|
||||
criterion: CcCriterion<WindowClit>,
|
||||
matched: Rc<Matched>,
|
||||
leaf: Option<Rc<CritLeafMatcher<ToplevelData>>>,
|
||||
}
|
||||
|
||||
struct Matched {
|
||||
slf: Weak<ControlCenterInner>,
|
||||
windows: CopyHashMap<ToplevelIdentifier, ()>,
|
||||
}
|
||||
|
||||
impl Matched {
|
||||
fn request_frame(&self) {
|
||||
if let Some(slf) = self.slf.upgrade() {
|
||||
slf.window.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
pub fn create_window_search_pane(self: &Rc<Self>) -> WindowSearchPane {
|
||||
let mut pane = WindowSearchPane {
|
||||
state: self.state.clone(),
|
||||
criterion: Default::default(),
|
||||
matched: Rc::new(Matched {
|
||||
slf: Rc::downgrade(self),
|
||||
windows: Default::default(),
|
||||
}),
|
||||
leaf: Default::default(),
|
||||
};
|
||||
pane.update_matcher();
|
||||
pane
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowSearchPane {
|
||||
pub fn title(&self, res: &mut String) {
|
||||
res.push_str("Window Search");
|
||||
}
|
||||
|
||||
pub fn show(&mut self, behavior: &mut CcBehavior<'_>, ui: &mut Ui) {
|
||||
let mut clear = false;
|
||||
if self.criterion.show(ui) {
|
||||
clear = self.update_matcher();
|
||||
}
|
||||
ui.separator();
|
||||
let mut windows: Vec<_> = self.matched.windows.lock().keys().copied().collect();
|
||||
windows.sort();
|
||||
for id in windows {
|
||||
let Some(window) = self.state.toplevels.get(&id).and_then(|v| v.upgrade()) else {
|
||||
continue;
|
||||
};
|
||||
show_window_collapsible(behavior, ui, &window);
|
||||
}
|
||||
if clear {
|
||||
self.matched.windows.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_matcher(&mut self) -> bool {
|
||||
let mut clear = false;
|
||||
let state = &self.state;
|
||||
if let Some(new) = self.criterion.to_crit(state) {
|
||||
clear = true;
|
||||
let matched = self.matched.clone();
|
||||
let leaf = state.tl_matcher_manager.leaf(&new, move |data| {
|
||||
matched.windows.set(data, ());
|
||||
matched.request_frame();
|
||||
Box::new({
|
||||
let matched = matched.clone();
|
||||
move || {
|
||||
matched.windows.remove(&data);
|
||||
matched.request_frame();
|
||||
}
|
||||
})
|
||||
});
|
||||
state.tl_matcher_manager.rematch_all(state);
|
||||
if self.criterion.any(|c| matches!(c, WindowClit::Client(_))) {
|
||||
state.cl_matcher_manager.rematch_all(state);
|
||||
}
|
||||
self.leaf = Some(leaf);
|
||||
}
|
||||
clear
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowPane {
|
||||
window: Rc<dyn ToplevelNode>,
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
pub fn create_window_pane(self: &Rc<Self>, window: &Rc<dyn ToplevelNode>) -> WindowPane {
|
||||
WindowPane {
|
||||
window: window.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowPane {
|
||||
pub fn title(&self, res: &mut String) {
|
||||
res.push_str("Window");
|
||||
}
|
||||
|
||||
pub fn show(&mut self, behavior: &mut CcBehavior<'_>, ui: &mut Ui) {
|
||||
show_window(behavior, ui, &*self.window)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_window_collapsible(
|
||||
behavior: &mut CcBehavior,
|
||||
ui: &mut Ui,
|
||||
window: &Rc<dyn ToplevelNode>,
|
||||
) {
|
||||
let data = window.tl_data();
|
||||
let mut layout_job = LayoutJob::default();
|
||||
layout_job.append(
|
||||
"Window",
|
||||
0.0,
|
||||
TextFormat {
|
||||
color: ui.style().visuals.widgets.inactive.text_color(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
layout_job.append(
|
||||
&data.title.borrow(),
|
||||
10.0,
|
||||
TextFormat {
|
||||
color: ui.style().visuals.widgets.active.text_color(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let closed = CollapsingHeader::new(layout_job)
|
||||
.id_salt(("window", data.identifier.get()))
|
||||
.show(ui, |ui| {
|
||||
if icon_label(ICON_OPEN_IN_NEW)
|
||||
.sense(Sense::CLICK)
|
||||
.ui(ui)
|
||||
.clicked()
|
||||
{
|
||||
behavior.open = Some(PaneType::Window(behavior.cc.create_window_pane(window)));
|
||||
}
|
||||
show_window(behavior, ui, &**window)
|
||||
})
|
||||
.fully_closed();
|
||||
if closed {
|
||||
ensure_listener(ui, behavior, data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_window(behavior: &mut CcBehavior<'_>, ui: &mut Ui, window: &dyn ToplevelNode) {
|
||||
let data = window.tl_data();
|
||||
ensure_listener(ui, behavior, data);
|
||||
grid(ui, ("window", data.identifier.get()), |ui| {
|
||||
label(ui, "ID", &*data.identifier.get().to_string());
|
||||
label(ui, "Title", &*data.title.borrow());
|
||||
if let Some(w) = data.workspace.get() {
|
||||
label(ui, "Workspace", &w.name);
|
||||
}
|
||||
match &data.kind {
|
||||
ToplevelType::Container => {
|
||||
label(ui, "Type", "Container");
|
||||
}
|
||||
ToplevelType::Placeholder(_) => {
|
||||
label(ui, "Type", "Placeholder");
|
||||
}
|
||||
ToplevelType::XdgToplevel(t) => {
|
||||
label(ui, "Type", "xdg_toplevel");
|
||||
let tag = &*t.tag.borrow();
|
||||
if tag.is_not_empty() {
|
||||
label(ui, "Tag", tag);
|
||||
}
|
||||
}
|
||||
ToplevelType::XWindow(t) => {
|
||||
label(ui, "Type", "X Window");
|
||||
if let Some(class) = &*t.info.class.borrow() {
|
||||
label(ui, "Class", class);
|
||||
}
|
||||
if let Some(instance) = &*t.info.instance.borrow() {
|
||||
label(ui, "Instance", instance);
|
||||
}
|
||||
if let Some(role) = &*t.info.role.borrow() {
|
||||
label(ui, "Role", role);
|
||||
}
|
||||
}
|
||||
}
|
||||
let app_id = &*data.app_id.borrow();
|
||||
if app_id.is_not_empty() {
|
||||
label(ui, "App ID", app_id);
|
||||
}
|
||||
read_only_bool(ui, "Floating", data.parent_is_float.get());
|
||||
read_only_bool(ui, "Visible", data.visible.get());
|
||||
read_only_bool(ui, "Urgent", data.wants_attention.get());
|
||||
read_only_bool(ui, "Fullscreen", data.is_fullscreen.get());
|
||||
if let Some(ct) = data.content_type.get() {
|
||||
label(ui, "Content Type", ct.text());
|
||||
}
|
||||
});
|
||||
if let Some(client) = &data.client {
|
||||
show_client_collapsible(behavior, ui, client);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_listener(ui: &mut Ui, behavior: &CcBehavior<'_>, data: &ToplevelData) {
|
||||
ui.memory_mut(|m| {
|
||||
m.caches
|
||||
.cache::<WindowPropertyListeners>()
|
||||
.ensure(behavior.cc, data);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WindowPropertyListeners {
|
||||
generation: u64,
|
||||
listeners: AHashMap<NodeId, WindowPropertyListener>,
|
||||
}
|
||||
|
||||
struct WindowPropertyListener {
|
||||
_listener: EventListener<dyn LazyEventSourceListener>,
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
impl WindowPropertyListeners {
|
||||
fn ensure(&mut self, cc: &Rc<ControlCenterInner>, data: &ToplevelData) {
|
||||
let listener = self.listeners.entry(data.node_id).or_insert_with(|| {
|
||||
let listener =
|
||||
EventListener::new(Rc::downgrade(cc) as Weak<dyn LazyEventSourceListener>);
|
||||
listener.attach(data.property_changed_source());
|
||||
WindowPropertyListener {
|
||||
_listener: listener,
|
||||
generation: 0,
|
||||
}
|
||||
});
|
||||
listener.generation = self.generation;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for WindowPropertyListeners {}
|
||||
unsafe impl Send for WindowPropertyListeners {}
|
||||
|
||||
impl CacheTrait for WindowPropertyListeners {
|
||||
fn update(&mut self) {
|
||||
self.listeners
|
||||
.retain(|_, m| m.generation == self.generation);
|
||||
self.generation += 1;
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.listeners.len()
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn show_content_types(ui: &mut Ui, ct: &mut ContentType) -> bool {
|
||||
let mut v = *ct;
|
||||
let mut photo = (v & PHOTO_CONTENT).0 != 0;
|
||||
let mut video = (v & VIDEO_CONTENT).0 != 0;
|
||||
let mut game = (v & GAME_CONTENT).0 != 0;
|
||||
ui.checkbox(&mut photo, "Photo");
|
||||
ui.checkbox(&mut video, "Video");
|
||||
ui.checkbox(&mut game, "Game");
|
||||
v = NO_CONTENT_TYPE;
|
||||
if photo {
|
||||
v |= PHOTO_CONTENT;
|
||||
}
|
||||
if video {
|
||||
v |= VIDEO_CONTENT;
|
||||
}
|
||||
if game {
|
||||
v |= GAME_CONTENT;
|
||||
}
|
||||
mem::replace(ct, v) != v
|
||||
}
|
||||
|
|
@ -234,7 +234,6 @@ impl ClMatcherManager {
|
|||
self.root(ClmMatchTag::new(string))
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn id(&self, id: ClientId) -> Rc<ClmUpstreamNode> {
|
||||
self.root(ClmMatchId(id))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -936,7 +936,6 @@ impl ToplevelData {
|
|||
parent.node_is_workspace()
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn property_changed_source(&self) -> &Rc<LazyEventSource> {
|
||||
self.property_changed_source
|
||||
.get_or_init(|| self.state.lazy_event_sources.create_source())
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use {
|
|||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct Opaque {
|
||||
lo: u64,
|
||||
hi: u64,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Ord, PartialOrd)]
|
||||
pub struct ToplevelIdentifier(Opaque);
|
||||
|
||||
unsafe impl UnsafeCellCloneSafe for ToplevelIdentifier {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue