Files
hcie-rust-v3.05/patches/grimdock/examples/6_policy_controls.rs
T
2026-07-09 02:59:53 +03:00

333 lines
13 KiB
Rust

//! Policy controls example:
//! `cargo run --example 6_policy_controls`
use eframe::egui;
use grimdock::{
DropPolicy, PaneAnchor, PaneBuilder, PaneId, PaneMenuAction, PaneOptions, PaneRole,
PanelContext, PanelStyle, PanelTree, Tab, TabDropPolicy,
};
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("grimdock 6_policy_controls")
.with_inner_size([1180.0, 720.0]),
..Default::default()
};
eframe::run_native(
"grimdock 6_policy_controls",
options,
Box::new(|_cc| Ok(Box::new(App::new()))),
)
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum TabId {
Files,
Editor,
Search,
Terminal,
}
struct App {
tree: PanelTree<TabId>,
style: PanelStyle,
files_home_pane: PaneId,
editor_home_pane: PaneId,
terminal_pane: PaneId,
persist_files_pane: bool,
lock_files_layout: bool,
disable_files_drops: bool,
disable_terminal_resize: bool,
disable_editor_reorder: bool,
disable_editor_drag_out: bool,
freeze_search_tab: bool,
lock_search_to_files_pane: bool,
allow_search_only_files_and_editor: bool,
block_search_from_terminal: bool,
lock_search_to_sidebar_role: bool,
allow_search_only_sidebar_and_editor_roles: bool,
block_search_from_terminal_role: bool,
pane_action_log: Vec<String>,
}
impl App {
fn new() -> Self {
let (tree, files_home_pane, editor_home_pane, terminal_pane) = make_tree();
Self {
tree,
style: PanelStyle::default(),
files_home_pane,
editor_home_pane,
terminal_pane,
persist_files_pane: true,
lock_files_layout: false,
disable_files_drops: false,
disable_terminal_resize: false,
disable_editor_reorder: false,
disable_editor_drag_out: false,
freeze_search_tab: false,
lock_search_to_files_pane: false,
allow_search_only_files_and_editor: false,
block_search_from_terminal: false,
lock_search_to_sidebar_role: false,
allow_search_only_sidebar_and_editor_roles: false,
block_search_from_terminal_role: false,
pane_action_log: Vec::new(),
}
}
fn reset_layout(&mut self) {
let (tree, files_home_pane, editor_home_pane, terminal_pane) = make_tree();
self.tree = tree;
self.files_home_pane = files_home_pane;
self.editor_home_pane = editor_home_pane;
self.terminal_pane = terminal_pane;
}
fn update_pane_options(
&mut self,
pane_id: PaneId,
update: impl FnOnce(&mut PaneOptions),
) {
if let Some(mut pane) = self.tree.pane_mut(pane_id) {
let mut options = pane.options();
update(&mut options);
pane.set_options(options);
}
}
fn apply_policy_toggles(&mut self) {
let persist_files_pane = self.persist_files_pane;
let lock_files_layout = self.lock_files_layout;
let disable_files_drops = self.disable_files_drops;
let disable_terminal_resize = self.disable_terminal_resize;
let disable_editor_reorder = self.disable_editor_reorder;
let disable_editor_drag_out = self.disable_editor_drag_out;
let freeze_search_tab = self.freeze_search_tab;
let lock_search_to_files_pane = self.lock_search_to_files_pane;
let allow_search_only_files_and_editor = self.allow_search_only_files_and_editor;
let block_search_from_terminal = self.block_search_from_terminal;
let lock_search_to_sidebar_role = self.lock_search_to_sidebar_role;
let allow_search_only_sidebar_and_editor_roles =
self.allow_search_only_sidebar_and_editor_roles;
let block_search_from_terminal_role = self.block_search_from_terminal_role;
let files_home_pane = self.files_home_pane;
let editor_home_pane = self.editor_home_pane;
let terminal_pane = self.terminal_pane;
self.update_pane_options(files_home_pane, |options| {
options.persist_when_empty = persist_files_pane;
options.lock_layout = lock_files_layout;
options.drop_policy = if disable_files_drops {
DropPolicy::none()
} else {
DropPolicy::all()
};
});
self.update_pane_options(editor_home_pane, |options| {
options.allow_tab_reorder = !disable_editor_reorder;
options.allow_tab_drag_out = !disable_editor_drag_out;
});
self.update_pane_options(terminal_pane, |options| {
options.allow_resize = !disable_terminal_resize;
});
if let Some(search_pane_id) = self.tree.find_pane_containing(&TabId::Search) {
if let Some(mut pane) = self.tree.pane_mut(search_pane_id) {
if let Some(tab) = pane.tabs_mut().iter_mut().find(|tab| tab.id == TabId::Search) {
tab.draggable = !freeze_search_tab;
let mut drop_policy = TabDropPolicy::default();
if lock_search_to_files_pane {
drop_policy.locked_to_pane = Some(files_home_pane);
} else if lock_search_to_sidebar_role {
drop_policy.locked_to_role = Some(PaneRole::Sidebar);
} else if allow_search_only_files_and_editor {
drop_policy.allowed_panes = Some(vec![files_home_pane, editor_home_pane]);
} else if allow_search_only_sidebar_and_editor_roles {
drop_policy.allowed_roles =
Some(vec![PaneRole::Sidebar, PaneRole::Editor]);
}
if block_search_from_terminal {
drop_policy.blocked_panes.push(terminal_pane);
}
if block_search_from_terminal_role {
drop_policy.blocked_roles.push(PaneRole::Terminal);
}
tab.drop_policy = drop_policy;
}
}
}
}
fn pane_present(&self, pane_id: PaneId) -> bool {
self.tree.pane(pane_id).is_some()
}
}
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.apply_policy_toggles();
egui::Panel::left("policy_controls")
.resizable(false)
.default_width(320.0)
.show(ctx, |ui| {
ui.heading("Policy controls");
ui.label("This example separates pane policy from tab policy.");
ui.separator();
ui.label("Pane policy");
ui.checkbox(&mut self.persist_files_pane, "Persist Files pane when empty");
ui.checkbox(&mut self.lock_files_layout, "Lock Files pane layout");
ui.checkbox(&mut self.disable_files_drops, "Reject drops into Files pane");
ui.checkbox(&mut self.disable_terminal_resize, "Disable Terminal resize");
ui.checkbox(&mut self.disable_editor_reorder, "Disable Editor pane reorder");
ui.checkbox(&mut self.disable_editor_drag_out, "Disable Editor pane drag-out");
ui.separator();
ui.label("Tab policy");
ui.checkbox(&mut self.freeze_search_tab, "Make Search tab immovable");
ui.checkbox(&mut self.lock_search_to_files_pane, "Lock Search tab to Files pane ID");
ui.checkbox(&mut self.lock_search_to_sidebar_role, "Lock Search tab to Sidebar role");
ui.checkbox(
&mut self.allow_search_only_files_and_editor,
"Allow Search only in Files and Editor pane IDs",
);
ui.checkbox(
&mut self.allow_search_only_sidebar_and_editor_roles,
"Allow Search only in Sidebar and Editor roles",
);
ui.checkbox(&mut self.block_search_from_terminal, "Block Search from Terminal pane ID");
ui.checkbox(
&mut self.block_search_from_terminal_role,
"Block Search from Terminal role",
);
ui.separator();
if ui.button("Reset layout").clicked() {
self.reset_layout();
}
ui.separator();
ui.label(format!(
"Files home pane present: {}",
if self.pane_present(self.files_home_pane) { "yes" } else { "no" }
));
ui.label(format!(
"Files anchor owner: {:?}",
self.tree.find_pane_with_anchor(PaneAnchor::Left)
));
ui.label(format!(
"Terminal anchor owner: {:?}",
self.tree.find_pane_with_anchor(PaneAnchor::Bottom)
));
ui.label(format!(
"Sidebar role owner: {:?}",
self.tree.find_pane_with_role(PaneRole::Sidebar)
));
ui.label(format!(
"Terminal role owner: {:?}",
self.tree.find_pane_with_role(PaneRole::Terminal)
));
ui.label(format!(
"Search tab currently in pane: {:?}",
self.tree.find_pane_containing(&TabId::Search)
));
ui.separator();
ui.label("What to try:");
ui.label("1. Move Files out of its home pane. With persistence on, the anchored left pane stays as an empty drop target.");
ui.label("2. Turn off Files persistence, empty that pane, and notice the pane can now disappear.");
ui.label("3. Lock Search to the Files pane, then try dropping it into Editor or Terminal.");
ui.label("4. Switch between pane-ID and pane-role targeting to compare semantic versus identity-based policy.");
if let Some(last) = self.pane_action_log.last() {
ui.separator();
ui.label(format!("Last custom pane action: {last}"));
}
});
egui::CentralPanel::default()
.frame(egui::Frame::NONE)
.show(ctx, |ui| {
let output = PanelContext::new(ui, &mut self.tree, &self.style)
.with_pane_menu_provider(&|pane_id, tree| {
let mut actions = vec![PaneMenuAction::new("inspect", "Inspect pane")];
if tree.find_pane_with_anchor(PaneAnchor::Left) == Some(pane_id) {
actions.push(PaneMenuAction::new("mark_left", "Mark left anchor"));
}
actions
})
.show(|ui, tab_id| match tab_id {
TabId::Files => {
ui.heading("Files");
ui.label("This pane demonstrates pane persistence, pane locking, and pane drop policy.");
}
TabId::Editor => {
ui.heading("Editor");
ui.label("This pane demonstrates pane-level reorder and drag-out controls.");
}
TabId::Search => {
ui.heading("Search");
ui.label("This tab demonstrates draggable state plus pane lock / allow-list / block-list targeting.");
}
TabId::Terminal => {
ui.heading("Terminal");
ui.label("This pane demonstrates resize locking and can also be blocked as a tab destination.");
}
});
for action in output.pane_actions {
self.pane_action_log
.push(format!("{} on pane {:?}", action.action_id, action.pane_id));
}
});
}
}
fn make_tree() -> (PanelTree<TabId>, PaneId, PaneId, PaneId) {
let mut tree = PanelTree::new(vec![
Tab::new("editor", TabId::Editor).with_leading_visual(">"),
Tab::new("search", TabId::Search).with_leading_visual("?"),
]);
let files_home_pane = tree.ensure_pane_at_anchor(
PaneAnchor::Left,
PaneBuilder::new(Tab::new("files", TabId::Files).with_leading_visual("#")),
);
let editor_home_pane = tree
.find_pane_containing(&TabId::Editor)
.expect("editor pane should exist after initial split");
let terminal_pane = tree.ensure_pane_at_anchor(
PaneAnchor::Bottom,
PaneBuilder::new(Tab::new("terminal", TabId::Terminal).with_leading_visual("$")),
);
if let Some(mut pane) = tree.pane_mut(files_home_pane) {
let mut options = pane.options();
options.persist_when_empty = true;
options.role = Some(PaneRole::Sidebar);
pane.set_options(options);
}
if let Some(mut pane) = tree.pane_mut(editor_home_pane) {
let mut options = pane.options();
options.role = Some(PaneRole::Editor);
pane.set_options(options);
}
if let Some(mut pane) = tree.pane_mut(terminal_pane) {
let mut options = pane.options();
options.role = Some(PaneRole::Terminal);
pane.set_options(options);
}
(tree, files_home_pane, editor_home_pane, terminal_pane)
}