control-center: add in-process control center
This commit is contained in:
parent
008e8a671a
commit
186d5b694b
28 changed files with 859 additions and 14 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
|
@ -407,6 +407,24 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_tiles"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef184e589f0a80560bd3b63017634642d1ba112a8a8d9b29341f7cafd04601f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"egui",
|
||||
"itertools",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.33.3"
|
||||
|
|
@ -681,6 +699,15 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1697e6b71679da96d5c41bb9035116141baadbf59a60625fd66cb3c9584e7b0"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
|
|
@ -722,6 +749,7 @@ dependencies = [
|
|||
"clap_complete",
|
||||
"dirs",
|
||||
"egui",
|
||||
"egui_tiles",
|
||||
"futures-util",
|
||||
"gpu-alloc",
|
||||
"gpu-alloc-types",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ with_builtin_macros = "0.1.0"
|
|||
blake3 = "1.8.2"
|
||||
run-on-drop = "1.0.0"
|
||||
egui = { version = "0.33.3", default-features = false }
|
||||
egui_tiles = { version = "0.14.1", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
repc = "0.1.1"
|
||||
|
|
|
|||
|
|
@ -1046,6 +1046,10 @@ impl ConfigClient {
|
|||
self.send(&ClientMessage::SetMiddleClickPasteEnabled { enabled });
|
||||
}
|
||||
|
||||
pub fn open_control_center(&self) {
|
||||
self.send(&ClientMessage::OpenControlCenter);
|
||||
}
|
||||
|
||||
pub fn set_workspace_display_order(&self, order: WorkspaceDisplayOrder) {
|
||||
self.send(&ClientMessage::SetWorkspaceDisplayOrder { order });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -845,6 +845,7 @@ pub enum ClientMessage<'a> {
|
|||
proportional: Option<Vec<&'a str>>,
|
||||
monospace: Option<Vec<&'a str>>,
|
||||
},
|
||||
OpenControlCenter,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -380,3 +380,8 @@ pub fn on_unload(f: impl FnOnce() + 'static) {
|
|||
pub fn set_middle_click_paste_enabled(enabled: bool) {
|
||||
get!().set_middle_click_paste_enabled(enabled);
|
||||
}
|
||||
|
||||
/// Opens the control center.
|
||||
pub fn open_control_center() {
|
||||
get!().open_control_center();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod clients;
|
||||
mod color;
|
||||
mod color_management;
|
||||
mod control_center;
|
||||
mod damage_tracking;
|
||||
mod duration;
|
||||
mod generate;
|
||||
|
|
@ -97,6 +98,8 @@ pub enum Cmd {
|
|||
Clients(ClientsArgs),
|
||||
/// Inspect the surface tree.
|
||||
Tree(TreeArgs),
|
||||
/// Opens the control center.
|
||||
ControlCenter,
|
||||
/// Prints the Jay version and exits.
|
||||
Version,
|
||||
/// Prints the Jay PID and exits.
|
||||
|
|
@ -243,5 +246,6 @@ pub fn main() {
|
|||
#[cfg(feature = "it")]
|
||||
Cmd::RunTests => crate::it::run_tests(),
|
||||
Cmd::Reexec(a) => reexec::main(cli.global, a),
|
||||
Cmd::ControlCenter => control_center::main(cli.global),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
src/cli/control_center.rs
Normal file
32
src/cli/control_center.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::GlobalArgs,
|
||||
tools::tool_client::{Handle, ToolClient, with_tool_client},
|
||||
wire::{jay_compositor, jay_open_control_center_request},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub fn main(global: GlobalArgs) {
|
||||
with_tool_client(global.log_level, |tc| async move {
|
||||
let cc = ControlCenter { tc: tc.clone() };
|
||||
cc.run().await;
|
||||
});
|
||||
}
|
||||
|
||||
struct ControlCenter {
|
||||
tc: Rc<ToolClient>,
|
||||
}
|
||||
|
||||
impl ControlCenter {
|
||||
async fn run(self) {
|
||||
let tc = &self.tc;
|
||||
let comp = tc.jay_compositor().await;
|
||||
let id = tc.id();
|
||||
tc.send(jay_compositor::OpenControlCenter { self_id: comp, id });
|
||||
jay_open_control_center_request::Failed::handle(&tc, id, (), |_, ev| {
|
||||
fatal!("Could not open the control center: {}", ev.msg);
|
||||
});
|
||||
tc.round_trip().await;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ use {
|
|||
clientmem::{self, ClientMemError},
|
||||
cmm::{cmm_manager::ColorManager, cmm_primaries::Primaries},
|
||||
config::ConfigProxy,
|
||||
control_center::redraw_control_centers,
|
||||
copy_device::CopyDeviceRegistry,
|
||||
cpu_worker::{CpuWorker, CpuWorkerError},
|
||||
criteria::{
|
||||
|
|
@ -393,6 +394,7 @@ fn start_compositor2(
|
|||
lazy_event_sources: Default::default(),
|
||||
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
||||
egg_state: Default::default(),
|
||||
control_centers: Default::default(),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
@ -417,6 +419,7 @@ fn start_compositor2(
|
|||
let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
|
||||
ring.run()?;
|
||||
state.clear();
|
||||
engine.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -597,6 +600,10 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
|
|||
"lazy event sources",
|
||||
handle_lazy_event_sources(state.clone()),
|
||||
),
|
||||
eng.spawn(
|
||||
"redraw control centers",
|
||||
redraw_control_centers(state.clone()),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1811,6 +1811,12 @@ impl ConfigProxyHandler {
|
|||
self.state.set_egui_fonts(proportional, monospace);
|
||||
}
|
||||
|
||||
fn handle_open_control_center(&self) {
|
||||
if let Err(e) = self.state.open_control_center() {
|
||||
log::error!("Could not open control center: {}", ErrorFmt(e));
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_set_log_level(&self, level: ConfigLogLevel) {
|
||||
self.state.set_log_level(level.into());
|
||||
}
|
||||
|
|
@ -3319,6 +3325,7 @@ impl ConfigProxyHandler {
|
|||
proportional,
|
||||
monospace,
|
||||
} => self.handle_set_egui_fonts(proportional, monospace),
|
||||
ClientMessage::OpenControlCenter => self.handle_open_control_center(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
606
src/control_center.rs
Normal file
606
src/control_center.rs
Normal file
|
|
@ -0,0 +1,606 @@
|
|||
use {
|
||||
crate::{
|
||||
egui_adapter::egui_platform::{
|
||||
EggError, EggWindow, EggWindowOwner,
|
||||
icons::{ICON_CLOSE, ICON_DRAG_INDICATOR, ICON_INFO},
|
||||
},
|
||||
macros::Bitflag,
|
||||
state::State,
|
||||
utils::{
|
||||
asyncevent::AsyncEvent, copyhashmap::CopyHashMap, numcell::NumCell,
|
||||
static_text::StaticText,
|
||||
},
|
||||
},
|
||||
egui::{
|
||||
Align, CentralPanel, Checkbox, Color32, ComboBox, Context, CursorIcon, DragValue, Frame,
|
||||
Grid, InnerResponse, Label, Layout, Response, Rgba, RichText, ScrollArea, Sense, SidePanel,
|
||||
Stroke, TextBuffer, TextEdit, Ui, UiBuilder, Visuals, Widget, WidgetText, emath::Numeric,
|
||||
vec2,
|
||||
},
|
||||
egui_tiles::{ResizeState, TabState, Tile, TileId, Tiles, Tree},
|
||||
linearize::{Linearize, LinearizeExt},
|
||||
std::{
|
||||
cell::RefCell,
|
||||
hash::Hash,
|
||||
mem,
|
||||
ops::{Deref, DerefMut, RangeInclusive},
|
||||
rc::Rc,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
mod cc_sidebar;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ControlCenterError {
|
||||
#[error("Could not get the egg context")]
|
||||
GetEggContext(#[source] EggError),
|
||||
}
|
||||
|
||||
linear_ids!(ControlCenterIds, ControlCenterId, u64);
|
||||
|
||||
pub async fn redraw_control_centers(state: Rc<State>) {
|
||||
let cc = &state.control_centers;
|
||||
loop {
|
||||
cc.redraw.triggered().await;
|
||||
let interests = cc.change.take();
|
||||
for cc in cc.control_centers.lock().values() {
|
||||
if cc.inner.interests.interests.get().intersects(interests) {
|
||||
cc.inner.window.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ControlCenters {
|
||||
ids: ControlCenterIds,
|
||||
change: NumCell<ControlCenterInterest>,
|
||||
redraw: AsyncEvent,
|
||||
control_centers: CopyHashMap<ControlCenterId, Rc<ControlCenter>>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
ControlCenterInterest: u32;
|
||||
_UNUSED,
|
||||
}
|
||||
|
||||
pub struct ControlCenter {
|
||||
inner: Rc<ControlCenterInner>,
|
||||
}
|
||||
|
||||
linear_ids!(PaneIds, PaneId, u64);
|
||||
|
||||
struct ControlCenterInner {
|
||||
id: ControlCenterId,
|
||||
state: Rc<State>,
|
||||
tree: RefCell<Settings>,
|
||||
window: Rc<EggWindow>,
|
||||
pane_ids: PaneIds,
|
||||
interests: Rc<Interests>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Interests {
|
||||
interests: NumCell<ControlCenterInterest>,
|
||||
interests_array: [NumCell<u64>; <ControlCenterInterest as Bitflag>::Type::BITS as usize],
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
tree: Tree<Pane>,
|
||||
}
|
||||
|
||||
struct Pane {
|
||||
id: PaneId,
|
||||
ps: PaneState,
|
||||
own_interests: ControlCenterInterest,
|
||||
cc_interests: Rc<Interests>,
|
||||
ty: PaneType,
|
||||
}
|
||||
|
||||
struct PaneState {
|
||||
errors: Vec<String>,
|
||||
}
|
||||
|
||||
enum PaneType {}
|
||||
|
||||
struct CcBehavior<'a> {
|
||||
#[expect(dead_code)]
|
||||
cc: &'a Rc<ControlCenterInner>,
|
||||
close: Option<TileId>,
|
||||
open: Option<PaneType>,
|
||||
}
|
||||
|
||||
impl ControlCenters {
|
||||
pub fn clear(&self) {
|
||||
self.control_centers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Pane {
|
||||
fn title(&self, _res: &mut String) {
|
||||
match self.ty {}
|
||||
}
|
||||
|
||||
fn show(&mut self, _behavior: &mut CcBehavior<'_>, _ui: &mut Ui) {
|
||||
match self.ty {}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaneType {
|
||||
fn interest(&self) -> ControlCenterInterest {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl egui_tiles::Behavior<Pane> for CcBehavior<'_> {
|
||||
fn pane_ui(&mut self, ui: &mut Ui, tile_id: TileId, pane: &mut Pane) -> egui_tiles::UiResponse {
|
||||
let mut drag = false;
|
||||
Frame::central_panel(ui.style()).show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
drag = ui
|
||||
.add(icon_label(ICON_DRAG_INDICATOR).sense(Sense::drag()))
|
||||
.total_drag_delta()
|
||||
.map(|d| d.length() >= 5.0)
|
||||
.unwrap_or(false);
|
||||
let mut title = String::new();
|
||||
pane.title(&mut title);
|
||||
if ui
|
||||
.add(icon_label(&title).sense(Sense::click()))
|
||||
.middle_clicked()
|
||||
{
|
||||
self.close = Some(tile_id);
|
||||
}
|
||||
if ui
|
||||
.add(icon_label(ICON_CLOSE).sense(Sense::click()))
|
||||
.clicked()
|
||||
{
|
||||
self.close = Some(tile_id);
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
show_errors(ui, &mut pane.ps);
|
||||
ui.scope_builder(UiBuilder::new().id(("pane", pane.id)), |ui| {
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.allocate_space(vec2(ui.available_width(), 0.0));
|
||||
pane.show(self, ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
if drag {
|
||||
egui_tiles::UiResponse::DragStarted
|
||||
} else {
|
||||
egui_tiles::UiResponse::None
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_title_for_pane(&mut self, _pane: &Pane) -> WidgetText {
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn tab_hover_cursor_icon(&self) -> CursorIcon {
|
||||
CursorIcon::Default
|
||||
}
|
||||
|
||||
fn tab_title_for_tile(&mut self, tiles: &Tiles<Pane>, tile_id: TileId) -> WidgetText {
|
||||
fn add_title(tiles: &Tiles<Pane>, res: &mut String, first: &mut bool, tile_id: TileId) {
|
||||
if !mem::take(first) {
|
||||
res.push_str("/");
|
||||
}
|
||||
let Some(tile) = tiles.get(tile_id) else {
|
||||
res.push_str("MISSING TILE");
|
||||
return;
|
||||
};
|
||||
match tile {
|
||||
Tile::Pane(p) => p.title(res),
|
||||
Tile::Container(c) => {
|
||||
let mut first = true;
|
||||
for &tile_id in c.children() {
|
||||
add_title(tiles, res, &mut first, tile_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut res = String::new();
|
||||
let mut first = true;
|
||||
add_title(tiles, &mut res, &mut first, tile_id);
|
||||
res.into()
|
||||
}
|
||||
|
||||
fn on_tab_button(
|
||||
&mut self,
|
||||
_tiles: &Tiles<Pane>,
|
||||
tile_id: TileId,
|
||||
button_response: Response,
|
||||
) -> Response {
|
||||
if button_response.middle_clicked() {
|
||||
self.close = Some(tile_id);
|
||||
}
|
||||
button_response
|
||||
}
|
||||
|
||||
fn resize_stroke(&self, style: &egui::Style, resize_state: ResizeState) -> Stroke {
|
||||
match resize_state {
|
||||
ResizeState::Idle => style.visuals.widgets.noninteractive.bg_stroke,
|
||||
ResizeState::Hovering => style.visuals.widgets.hovered.fg_stroke,
|
||||
ResizeState::Dragging => style.visuals.widgets.active.fg_stroke,
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_bar_color(&self, visuals: &Visuals) -> Color32 {
|
||||
(Rgba::from(visuals.panel_fill) * Rgba::from_gray(0.8)).into()
|
||||
}
|
||||
|
||||
fn tab_bg_color(
|
||||
&self,
|
||||
visuals: &Visuals,
|
||||
_tiles: &Tiles<Pane>,
|
||||
_tile_id: TileId,
|
||||
state: &TabState,
|
||||
) -> Color32 {
|
||||
match state.active {
|
||||
true => visuals.panel_fill,
|
||||
false => self.tab_bar_color(visuals),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EggWindowOwner for ControlCenterInner {
|
||||
fn close(&self) {
|
||||
self.close();
|
||||
}
|
||||
|
||||
fn render(self: Rc<Self>, ctx: &Context) {
|
||||
let settings = &mut *self.tree.borrow_mut();
|
||||
SidePanel::left("sidebar").show(ctx, |ui| self.show_sidebar(&mut settings.tree, ui));
|
||||
CentralPanel::default()
|
||||
.frame(
|
||||
Frame::central_panel(&ctx.style())
|
||||
.outer_margin(0.0)
|
||||
.inner_margin(0.0),
|
||||
)
|
||||
.show(ctx, |ui| {
|
||||
let tree = &mut settings.tree;
|
||||
let mut behavior = CcBehavior {
|
||||
cc: &self,
|
||||
close: Default::default(),
|
||||
open: Default::default(),
|
||||
};
|
||||
tree.ui(&mut behavior, ui);
|
||||
if let Some(close) = behavior.close {
|
||||
tree.set_visible(close, false);
|
||||
tree.remove_recursively(close);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
if let Some(ty) = behavior.open {
|
||||
self.open(tree, ty);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn open_control_center(self: &Rc<Self>) -> Result<Rc<ControlCenter>, ControlCenterError> {
|
||||
let ctx = self
|
||||
.get_egg_context()
|
||||
.map_err(ControlCenterError::GetEggContext)?;
|
||||
let window = ctx.create_window("Control Center");
|
||||
let cc = Rc::new(ControlCenter {
|
||||
inner: Rc::new(ControlCenterInner {
|
||||
id: self.control_centers.ids.next(),
|
||||
window,
|
||||
state: self.clone(),
|
||||
tree: RefCell::new(Settings {
|
||||
tree: Tree::new_tabs("abcd", vec![]),
|
||||
}),
|
||||
pane_ids: Default::default(),
|
||||
interests: Default::default(),
|
||||
}),
|
||||
});
|
||||
cc.inner.window.set_owner(Some(cc.inner.clone()));
|
||||
self.control_centers
|
||||
.control_centers
|
||||
.set(cc.inner.id, cc.clone());
|
||||
Ok(cc)
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn trigger_cci(&self, cci: ControlCenterInterest) {
|
||||
self.control_centers.change.or_assign(cci);
|
||||
self.control_centers.redraw.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
fn close(&self) {
|
||||
self.window.set_owner(None);
|
||||
self.tree.borrow_mut().tree = Tree::empty("");
|
||||
self.state.control_centers.control_centers.remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ControlCenter {
|
||||
fn drop(&mut self) {
|
||||
self.inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
fn create_pane(&self, ty: PaneType) -> Pane {
|
||||
let pane = Pane {
|
||||
id: self.pane_ids.next(),
|
||||
ps: PaneState {
|
||||
errors: Default::default(),
|
||||
},
|
||||
own_interests: ty.interest(),
|
||||
cc_interests: self.interests.clone(),
|
||||
ty,
|
||||
};
|
||||
let own = pane.own_interests;
|
||||
for (idx, v) in pane.cc_interests.interests_array.iter().enumerate() {
|
||||
let interest = ControlCenterInterest(1 << idx);
|
||||
if own.intersects(interest) && v.fetch_add(1) == 0 {
|
||||
pane.cc_interests.interests.or_assign(interest);
|
||||
}
|
||||
}
|
||||
pane
|
||||
}
|
||||
|
||||
fn open(&self, tree: &mut Tree<Pane>, ty: PaneType) {
|
||||
let _ = tree;
|
||||
#[expect(unused_variables, unreachable_code)]
|
||||
let pane = self.create_pane(ty);
|
||||
let id = tree.tiles.insert_pane(pane);
|
||||
if let Some(root) = tree.root
|
||||
&& let Some(tile) = tree.tiles.get_mut(root)
|
||||
{
|
||||
match tile {
|
||||
Tile::Container(c) => {
|
||||
c.add_child(id);
|
||||
}
|
||||
Tile::Pane(_) => {
|
||||
let root = tree.tiles.insert_tab_tile(vec![root, id]);
|
||||
tree.root = Some(root);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tree.root = Some(id);
|
||||
}
|
||||
tree.make_active(|t, _| t == id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pane {
|
||||
fn drop(&mut self) {
|
||||
let own = self.own_interests;
|
||||
for (idx, v) in self.cc_interests.interests_array.iter().enumerate() {
|
||||
let interest = ControlCenterInterest(1 << idx);
|
||||
if own.intersects(interest) && v.fetch_sub(1) == 1 {
|
||||
self.cc_interests.interests.and_assign(!interest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_label(icon: &str) -> Label {
|
||||
Label::new(icon).selectable(false)
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn grid_label(ui: &mut Ui, label: &str) {
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
ui.label(label);
|
||||
});
|
||||
}
|
||||
|
||||
fn grid_label_ui<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), add_contents)
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn tip(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
|
||||
icon_label(ICON_INFO).ui(ui).on_hover_ui(add_contents);
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn text_edit(ui: &mut Ui, v: &mut dyn TextBuffer) -> Response {
|
||||
TextEdit::singleline(v)
|
||||
.clip_text(false)
|
||||
.min_size(vec2(200.0, 0.0))
|
||||
.ui(ui)
|
||||
}
|
||||
|
||||
fn show_errors(ui: &mut Ui, pane: &mut PaneState) {
|
||||
if pane.errors.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut to_remove = None;
|
||||
for (idx, e) in pane.errors.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
Frame::new().inner_margin(5.0).show(ui, |ui| {
|
||||
if ui.button(ICON_CLOSE).clicked() {
|
||||
to_remove = Some(idx);
|
||||
}
|
||||
ui.label(
|
||||
RichText::new("Error:")
|
||||
.strong()
|
||||
.color(ui.style().visuals.error_fg_color),
|
||||
);
|
||||
ui.add(Label::new(e).wrap());
|
||||
});
|
||||
});
|
||||
}
|
||||
if let Some(idx) = to_remove {
|
||||
pane.errors.remove(idx);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn grid<R>(
|
||||
ui: &mut Ui,
|
||||
id_salt: impl Hash,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
let mut spacing = ui.spacing().item_spacing;
|
||||
spacing.x *= 3.0;
|
||||
Grid::new(id_salt).spacing(spacing).show(ui, add_contents)
|
||||
}
|
||||
|
||||
fn row<R>(ui: &mut Ui, name: &str, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
|
||||
row_ui(ui, name, |_| (), add_contents)
|
||||
}
|
||||
|
||||
fn row_ui<R, S>(
|
||||
ui: &mut Ui,
|
||||
name: &str,
|
||||
label: impl FnOnce(&mut Ui) -> S,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> R {
|
||||
let ui = &mut *ui.row();
|
||||
grid_label_ui(ui, |ui| {
|
||||
ui.label(name);
|
||||
label(ui);
|
||||
});
|
||||
add_contents(ui)
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn bool(ui: &mut Ui, name: &str, old: bool, set: impl FnOnce(bool)) {
|
||||
bool_ui(ui, name, |_| (), old, set);
|
||||
}
|
||||
|
||||
fn bool_ui<R>(
|
||||
ui: &mut Ui,
|
||||
name: &str,
|
||||
label: impl FnOnce(&mut Ui) -> R,
|
||||
mut v: bool,
|
||||
set: impl FnOnce(bool),
|
||||
) {
|
||||
row_ui(ui, name, label, |ui| {
|
||||
if Checkbox::without_text(&mut v).ui(ui).changed() {
|
||||
set(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn read_only_bool(ui: &mut Ui, name: &str, old: bool) {
|
||||
read_only_bool_ui(ui, name, |_| (), old);
|
||||
}
|
||||
|
||||
fn read_only_bool_ui<R>(ui: &mut Ui, name: &str, label: impl FnOnce(&mut Ui) -> R, mut v: bool) {
|
||||
row_ui(ui, name, label, |ui| {
|
||||
ui.add_enabled_ui(false, |ui| Checkbox::without_text(&mut v).ui(ui));
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn combo_box<T>(ui: &mut Ui, name: &str, old: T, set: impl FnOnce(T))
|
||||
where
|
||||
T: StaticText + Linearize + PartialEq + Copy,
|
||||
{
|
||||
combo_box_ui(ui, name, |_| (), old, set);
|
||||
}
|
||||
|
||||
fn combo_box_ui<R, T>(
|
||||
ui: &mut Ui,
|
||||
name: &str,
|
||||
label: impl FnOnce(&mut Ui) -> R,
|
||||
mut v: T,
|
||||
set: impl FnOnce(T),
|
||||
) where
|
||||
T: StaticText + Linearize + PartialEq + Copy,
|
||||
{
|
||||
row_ui(ui, name, label, |ui| {
|
||||
let old = v;
|
||||
ComboBox::from_id_salt(name)
|
||||
.selected_text(v.text())
|
||||
.show_ui(ui, |ui| {
|
||||
for s in T::variants() {
|
||||
ui.selectable_value(&mut v, s, s.text());
|
||||
}
|
||||
});
|
||||
if old != v {
|
||||
set(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn drag_value<N>(
|
||||
ui: &mut Ui,
|
||||
name: &str,
|
||||
old: N,
|
||||
range: RangeInclusive<N>,
|
||||
speed: f64,
|
||||
set: impl FnOnce(N),
|
||||
) where
|
||||
N: Numeric,
|
||||
{
|
||||
drag_value_ui(ui, name, |_| (), old, range, speed, set);
|
||||
}
|
||||
|
||||
fn drag_value_ui<R, N>(
|
||||
ui: &mut Ui,
|
||||
name: &str,
|
||||
label: impl FnOnce(&mut Ui) -> R,
|
||||
mut v: N,
|
||||
range: RangeInclusive<N>,
|
||||
speed: f64,
|
||||
set: impl FnOnce(N),
|
||||
) where
|
||||
N: Numeric,
|
||||
{
|
||||
row_ui(ui, name, label, |ui| {
|
||||
if DragValue::new(&mut v)
|
||||
.range(range)
|
||||
.speed(speed)
|
||||
.ui(ui)
|
||||
.changed()
|
||||
{
|
||||
set(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn label(ui: &mut Ui, name: &str, text: impl Into<WidgetText>) {
|
||||
row(ui, name, |ui| ui.label(text));
|
||||
}
|
||||
|
||||
trait GridExt {
|
||||
fn row(&mut self) -> impl DerefMut<Target = Ui>;
|
||||
}
|
||||
|
||||
impl GridExt for Ui {
|
||||
fn row(&mut self) -> impl DerefMut<Target = Ui> {
|
||||
GridRow { ui: self }
|
||||
}
|
||||
}
|
||||
|
||||
struct GridRow<'a> {
|
||||
ui: &'a mut Ui,
|
||||
}
|
||||
|
||||
impl Deref for GridRow<'_> {
|
||||
type Target = Ui;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.ui
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for GridRow<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.ui
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GridRow<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.end_row();
|
||||
}
|
||||
}
|
||||
48
src/control_center/cc_sidebar.rs
Normal file
48
src/control_center/cc_sidebar.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use {
|
||||
crate::control_center::{ControlCenterInner, Pane},
|
||||
egui::{Align, Layout, ScrollArea, Ui, ViewportCommand},
|
||||
egui_tiles::Tree,
|
||||
linearize::{Linearize, LinearizeExt},
|
||||
std::{rc::Rc, sync::LazyLock},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Linearize)]
|
||||
enum PaneName {}
|
||||
|
||||
impl PaneName {
|
||||
fn name(self) -> &'static str {
|
||||
match self {}
|
||||
}
|
||||
}
|
||||
|
||||
static TYPES: LazyLock<Vec<PaneName>> = LazyLock::new(|| {
|
||||
let mut res: Vec<_> = PaneName::variants().collect();
|
||||
res.sort_by_key(|t| t.name());
|
||||
res
|
||||
});
|
||||
|
||||
impl ControlCenterInner {
|
||||
pub fn show_sidebar(self: &Rc<Self>, tree: &mut Tree<Pane>, ui: &mut Ui) {
|
||||
ui.with_layout(
|
||||
Layout::top_down(Align::Center).with_cross_justify(true),
|
||||
|ui| {
|
||||
ui.add_space(6.0);
|
||||
if ui.button("Close").clicked() {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::Close);
|
||||
}
|
||||
ui.separator();
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
for &ty in &*TYPES {
|
||||
if ui.button(ty.name()).clicked() {
|
||||
let _ty = match ty {};
|
||||
#[expect(unreachable_code)]
|
||||
self.open(tree, _ty);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
}
|
||||
ui.add_space(3.0);
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -112,11 +112,8 @@ pub enum EggError {
|
|||
pub mod icons {
|
||||
#[expect(dead_code)]
|
||||
pub const ICON_ADD: &str = "\u{e145}";
|
||||
#[expect(dead_code)]
|
||||
pub const ICON_CLOSE: &str = "\u{e5cd}";
|
||||
#[expect(dead_code)]
|
||||
pub const ICON_DRAG_INDICATOR: &str = "\u{e945}";
|
||||
#[expect(dead_code)]
|
||||
pub const ICON_INFO: &str = "\u{e88e}";
|
||||
#[expect(dead_code)]
|
||||
pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}";
|
||||
|
|
@ -361,7 +358,6 @@ impl EggState {
|
|||
}
|
||||
|
||||
impl State {
|
||||
#[expect(dead_code)]
|
||||
pub fn get_egg_context(self: &Rc<Self>) -> Result<Rc<EggContext>, EggError> {
|
||||
if let Some(ctx) = self.egg_state.ctx.get() {
|
||||
return Ok(ctx);
|
||||
|
|
@ -467,7 +463,6 @@ impl State {
|
|||
}
|
||||
|
||||
impl EggContext {
|
||||
#[expect(dead_code)]
|
||||
pub fn create_window(self: &Rc<Self>, title: &str) -> Rc<EggWindow> {
|
||||
let i = &self.inner;
|
||||
let wl_surface = i.wl_compositor.create_surface();
|
||||
|
|
@ -628,12 +623,10 @@ impl EggSeatInner {
|
|||
}
|
||||
|
||||
impl EggWindow {
|
||||
#[expect(dead_code)]
|
||||
pub fn request_redraw(&self) {
|
||||
self.inner.want_frame();
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn set_owner(&self, owner: Option<Rc<dyn EggWindowOwner>>) {
|
||||
self.inner.owner.set(owner);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub mod jay_ei_session_builder;
|
|||
pub mod jay_idle;
|
||||
pub mod jay_input;
|
||||
pub mod jay_log_file;
|
||||
pub mod jay_open_control_center_request;
|
||||
pub mod jay_output;
|
||||
pub mod jay_pointer;
|
||||
pub mod jay_popup_ext_manager_v1;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use {
|
|||
jay_idle::JayIdle,
|
||||
jay_input::JayInput,
|
||||
jay_log_file::JayLogFile,
|
||||
jay_open_control_center_request::JayOpenControlCenterRequest,
|
||||
jay_output::JayOutput,
|
||||
jay_pointer::JayPointer,
|
||||
jay_randr::JayRandr,
|
||||
|
|
@ -77,7 +78,7 @@ global_base!(JayCompositorGlobal, JayCompositor, JayCompositorError);
|
|||
|
||||
impl Global for JayCompositorGlobal {
|
||||
fn version(&self) -> u32 {
|
||||
27
|
||||
28
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
|
|
@ -541,6 +542,25 @@ impl JayCompositorRequestHandler for JayCompositor {
|
|||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open_control_center(
|
||||
&self,
|
||||
req: OpenControlCenter,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let obj = Rc::new(JayOpenControlCenterRequest {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
});
|
||||
track!(self.client, obj);
|
||||
self.client.add_client_obj(&obj)?;
|
||||
if let Err(e) = self.client.state.open_control_center() {
|
||||
obj.send_failed(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
|
|
|
|||
53
src/ifs/jay_open_control_center_request.rs
Normal file
53
src/ifs/jay_open_control_center_request.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::errorfmt::ErrorFmt,
|
||||
wire::{JayOpenControlCenterRequestId, jay_open_control_center_request::*},
|
||||
},
|
||||
std::{error::Error, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct JayOpenControlCenterRequest {
|
||||
pub id: JayOpenControlCenterRequestId,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
impl JayOpenControlCenterRequest {
|
||||
pub fn send_failed(&self, err: impl Error) {
|
||||
let msg = &ErrorFmt(err).to_string();
|
||||
self.client.event(Failed {
|
||||
self_id: self.id,
|
||||
msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl JayOpenControlCenterRequestRequestHandler for JayOpenControlCenterRequest {
|
||||
type Error = JayOpenControlCenterRequestError;
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = JayOpenControlCenterRequest;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for JayOpenControlCenterRequest {}
|
||||
|
||||
simple_add_obj!(JayOpenControlCenterRequest);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JayOpenControlCenterRequestError {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(JayOpenControlCenterRequestError, ClientError);
|
||||
|
|
@ -58,6 +58,7 @@ mod clientmem;
|
|||
mod cmm;
|
||||
mod compositor;
|
||||
mod config;
|
||||
mod control_center;
|
||||
mod copy_device;
|
||||
mod cpu_worker;
|
||||
mod criteria;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use {
|
|||
cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager},
|
||||
compositor::{LIBEI_SOCKET, LogLevel},
|
||||
config::ConfigProxy,
|
||||
control_center::ControlCenters,
|
||||
copy_device::CopyDeviceRegistry,
|
||||
cpu_worker::CpuWorker,
|
||||
criteria::{clm::ClMatcherManager, tlm::TlMatcherManager},
|
||||
|
|
@ -297,6 +298,7 @@ pub struct State {
|
|||
pub lazy_event_sources: Rc<LazyEventSources>,
|
||||
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
||||
pub egg_state: EggState,
|
||||
pub control_centers: ControlCenters,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -1162,6 +1164,7 @@ impl State {
|
|||
self.lazy_event_sources.clear();
|
||||
self.bo_drop_queue.kill();
|
||||
self.egg_state.clear();
|
||||
self.control_centers.clear();
|
||||
}
|
||||
|
||||
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ impl ToolClient {
|
|||
self_id: s.registry,
|
||||
name: s.jay_compositor.0,
|
||||
interface: JayCompositor.name(),
|
||||
version: s.jay_compositor.1.min(27),
|
||||
version: s.jay_compositor.1.min(28),
|
||||
id: id.into(),
|
||||
});
|
||||
self.jay_compositor.set(Some(id));
|
||||
|
|
|
|||
|
|
@ -100,6 +100,14 @@ impl<T> NumCell<T> {
|
|||
{
|
||||
!self.is_zero()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn take(&self) -> T
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
self.t.replace(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BitOr<Output = T> + Copy> BitOr<T> for &'_ NumCell<T> {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ pub enum SimpleCommand {
|
|||
ToggleSimpleImEnabled,
|
||||
ReloadSimpleIm,
|
||||
EnableUnicodeInput,
|
||||
OpenControlCenter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ impl ActionParser<'_> {
|
|||
"toggle-simple-im-enabled" => ToggleSimpleImEnabled,
|
||||
"reload-simple-im" => ReloadSimpleIm,
|
||||
"enable-unicode-input" => EnableUnicodeInput,
|
||||
"open-control-center" => OpenControlCenter,
|
||||
_ => {
|
||||
return Err(
|
||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ alt-m = "toggle-mono"
|
|||
alt-u = "toggle-fullscreen"
|
||||
|
||||
alt-f = "focus-parent"
|
||||
alt-c = "open-control-center"
|
||||
alt-shift-c = "close"
|
||||
alt-shift-f = "toggle-floating"
|
||||
Super_L = { type = "exec", exec = "alacritty" }
|
||||
|
|
|
|||
|
|
@ -36,10 +36,11 @@ use {
|
|||
is_reload,
|
||||
keyboard::Keymap,
|
||||
logging::set_log_level,
|
||||
on_devices_enumerated, on_idle, on_unload, quit, reload, set_color_management_enabled,
|
||||
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
|
||||
set_idle, set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
|
||||
set_show_float_pin_icon, set_show_titles, set_ui_drag_enabled, set_ui_drag_threshold,
|
||||
on_devices_enumerated, on_idle, on_unload, open_control_center, quit, reload,
|
||||
set_color_management_enabled, set_default_workspace_capture, set_explicit_sync_enabled,
|
||||
set_float_above_fullscreen, set_idle, set_idle_grace_period,
|
||||
set_middle_click_paste_enabled, set_show_bar, set_show_float_pin_icon, set_show_titles,
|
||||
set_ui_drag_enabled, set_ui_drag_threshold,
|
||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||
switch_to_vt,
|
||||
tasks::{self, JoinHandle},
|
||||
|
|
@ -245,6 +246,7 @@ impl Action {
|
|||
let persistent = state.persistent.clone();
|
||||
b.new(move || persistent.seat.enable_unicode_input())
|
||||
}
|
||||
SimpleCommand::OpenControlCenter => b.new(open_control_center),
|
||||
},
|
||||
Action::Multi { actions } => {
|
||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
|
|
|
|||
|
|
@ -1928,7 +1928,8 @@
|
|||
"disable-simple-im",
|
||||
"toggle-simple-im-enabled",
|
||||
"reload-simple-im",
|
||||
"enable-unicode-input"
|
||||
"enable-unicode-input",
|
||||
"open-control-center"
|
||||
]
|
||||
},
|
||||
"SimpleIm": {
|
||||
|
|
|
|||
|
|
@ -4427,6 +4427,10 @@ The string should have one of the following values:
|
|||
|
||||
This has no effect if the simple IM is not currently active.
|
||||
|
||||
- `open-control-center`:
|
||||
|
||||
Opens the control center.
|
||||
|
||||
|
||||
|
||||
<a name="types-SimpleIm"></a>
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,8 @@ SimpleActionName:
|
|||
Enables Unicode input in the simple, XCompose based input method.
|
||||
|
||||
This has no effect if the simple IM is not currently active.
|
||||
- value: open-control-center
|
||||
description: Opens the control center.
|
||||
|
||||
|
||||
Color:
|
||||
|
|
|
|||
|
|
@ -135,6 +135,10 @@ request get_pid (since = 27) {
|
|||
|
||||
}
|
||||
|
||||
request open_control_center (since = 28) {
|
||||
id: id(jay_open_control_center_request),
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event client_id {
|
||||
|
|
|
|||
7
wire/jay_open_control_center_request.txt
Normal file
7
wire/jay_open_control_center_request.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
request destroy {
|
||||
|
||||
}
|
||||
|
||||
event failed {
|
||||
msg: str,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue