Krolyxon
Commit 6efefdd

Branches

Tags

Refs →

bench51cratesbench51-guisrclayout.rs

362 lines · 11.4 KB · Rust

1//! Dockable panes: Keil's edit and debug window layouts on `egui_tiles`,
2//! with Nothing-style tab bars and flat surface cards.
3
4use eframe::egui::{
5 self, Align2, Color32, CornerRadius, Id, Rect, Response, Sense, Shape, Stroke, Ui, Vec2,
6 WidgetText, vec2,
7};
8use egui_tiles::{
9 Behavior, Container, LinearDir, ResizeState, SimplificationOptions, TabState, Tabs, Tile,
10 TileId, Tiles, Tree, UiResponse,
11};
12use serde::{Deserialize, Serialize};
13
14use crate::panes;
15use crate::state::State;
16use crate::theme::{self, palette};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub enum Pane {
20 Project,
21 Editor,
22 Build,
23 Registers,
24 Memory,
25 Disassembly,
26 Symbols,
27 Serial,
28 Ports,
29 Timers,
30 Interrupts,
31}
32
33impl Pane {
34 pub const ALL: [Pane; 11] = [
35 Pane::Project,
36 Pane::Editor,
37 Pane::Build,
38 Pane::Registers,
39 Pane::Disassembly,
40 Pane::Memory,
41 Pane::Symbols,
42 Pane::Serial,
43 Pane::Ports,
44 Pane::Timers,
45 Pane::Interrupts,
46 ];
47
48 pub fn title(self) -> &'static str {
49 match self {
50 Pane::Project => "Project",
51 Pane::Editor => "Editor",
52 Pane::Build => "Build Output",
53 Pane::Registers => "Registers",
54 Pane::Memory => "Memory",
55 Pane::Disassembly => "Disassembly",
56 Pane::Symbols => "Symbols",
57 Pane::Serial => "UART #1",
58 Pane::Ports => "Ports",
59 Pane::Timers => "Timers",
60 Pane::Interrupts => "Interrupts",
61 }
62 }
63
64 /// Panes this one is grouped with by default, to dock it next to them
65 /// when it is reopened.
66 fn neighbours(self) -> &'static [Pane] {
67 match self {
68 Pane::Project | Pane::Registers => &[Pane::Registers, Pane::Project],
69 Pane::Editor | Pane::Disassembly => &[Pane::Disassembly, Pane::Editor],
70 Pane::Build | Pane::Memory | Pane::Symbols => {
71 &[Pane::Memory, Pane::Build, Pane::Symbols]
72 }
73 Pane::Serial | Pane::Ports | Pane::Timers | Pane::Interrupts => &[
74 Pane::Serial,
75 Pane::Ports,
76 Pane::Timers,
77 Pane::Interrupts,
78 Pane::Memory,
79 ],
80 }
81 }
82}
83
84const GAP: f32 = 6.0;
85const TAB_BAR: f32 = 30.0;
86
87fn tabs(tiles: &mut Tiles<Pane>, panes: &[Pane]) -> TileId {
88 let ids = panes.iter().map(|&p| tiles.insert_pane(p)).collect();
89 tiles.insert_tab_tile(ids)
90}
91
92fn linear(tiles: &mut Tiles<Pane>, dir: LinearDir, children: &[(TileId, f32)]) -> TileId {
93 let ids: Vec<TileId> = children.iter().map(|c| c.0).collect();
94 let id = match dir {
95 LinearDir::Horizontal => tiles.insert_horizontal_tile(ids),
96 LinearDir::Vertical => tiles.insert_vertical_tile(ids),
97 };
98 if let Some(Tile::Container(Container::Linear(l))) = tiles.get_mut(id) {
99 for &(child, share) in children {
100 l.shares.set_share(child, share);
101 }
102 }
103 id
104}
105
106/// Keil's build-mode layout: project on the left, editor over build output.
107pub fn edit_tree() -> Tree<Pane> {
108 let mut t = Tiles::default();
109 let project = tabs(&mut t, &[Pane::Project]);
110 let editor = tabs(&mut t, &[Pane::Editor]);
111 let build = tabs(&mut t, &[Pane::Build]);
112 let right = linear(&mut t, LinearDir::Vertical, &[(editor, 0.7), (build, 0.3)]);
113 let root = linear(
114 &mut t,
115 LinearDir::Horizontal,
116 &[(project, 0.18), (right, 0.82)],
117 );
118 Tree::new(Id::unique("edit_layout"), root, t)
119}
120
121/// Keil's debug-mode layout: registers on the left, source and disassembly
122/// in the middle, memory and peripherals along the bottom.
123pub fn debug_tree() -> Tree<Pane> {
124 let mut t = Tiles::default();
125 let regs = tabs(&mut t, &[Pane::Registers, Pane::Project]);
126 let editor = tabs(&mut t, &[Pane::Editor]);
127 let disasm = tabs(&mut t, &[Pane::Disassembly]);
128 let mem = tabs(&mut t, &[Pane::Memory, Pane::Symbols, Pane::Build]);
129 let periph = tabs(
130 &mut t,
131 &[Pane::Serial, Pane::Ports, Pane::Timers, Pane::Interrupts],
132 );
133 let top = linear(
134 &mut t,
135 LinearDir::Horizontal,
136 &[(editor, 0.62), (disasm, 0.38)],
137 );
138 let bottom = linear(
139 &mut t,
140 LinearDir::Horizontal,
141 &[(mem, 0.56), (periph, 0.44)],
142 );
143 let right = linear(&mut t, LinearDir::Vertical, &[(top, 0.6), (bottom, 0.4)]);
144 let root = linear(
145 &mut t,
146 LinearDir::Horizontal,
147 &[(regs, 0.19), (right, 0.81)],
148 );
149 Tree::new(Id::unique("debug_layout"), root, t)
150}
151
152/// Brings `pane` to the front, docking it next to its usual neighbours if
153/// it was closed.
154pub fn show_pane(tree: &mut Tree<Pane>, pane: Pane) {
155 if let Some(id) = tree.tiles.find_pane(&pane) {
156 tree.make_active(|tid, _| tid == id);
157 return;
158 }
159 let new = tree.tiles.insert_pane(pane);
160 let target = pane
161 .neighbours()
162 .iter()
163 .filter_map(|n| tree.tiles.find_pane(n))
164 .filter_map(|id| tree.tiles.parent_of(id))
165 .find(|&parent| {
166 matches!(
167 tree.tiles.get(parent),
168 Some(Tile::Container(Container::Tabs(_)))
169 )
170 });
171 if let Some(parent) = target
172 && let Some(Tile::Container(Container::Tabs(t))) = tree.tiles.get_mut(parent)
173 {
174 t.add_child(new);
175 t.set_active(new);
176 return;
177 }
178 let tabs_id = tree.tiles.insert_container(Tabs::new(vec![new]));
179 match tree.root {
180 Some(root) => {
181 if let Some(Tile::Container(c)) = tree.tiles.get_mut(root) {
182 c.add_child(tabs_id);
183 } else {
184 tree.root = Some(tree.tiles.insert_horizontal_tile(vec![root, tabs_id]));
185 }
186 }
187 None => tree.root = Some(tabs_id),
188 }
189}
190
191/// Flat surface cards behind every visible tab group: the page shows
192/// through the gaps between them.
193pub fn card_shapes(tree: &Tree<Pane>, fill: Color32) -> Vec<Shape> {
194 tree.tiles
195 .iter()
196 .filter(|(_, t)| matches!(t, Tile::Container(Container::Tabs(_))))
197 .filter_map(|(&id, _)| tree.tiles.rect(id))
198 .map(|r| Shape::rect_filled(r, CornerRadius::same(8), fill))
199 .collect()
200}
201
202pub struct PaneBehavior<'a> {
203 pub st: &'a mut State,
204 /// Tile of the editor pane, whose group hides its tab bar.
205 pub editor_tile: Option<TileId>,
206}
207
208impl Behavior<Pane> for PaneBehavior<'_> {
209 fn pane_ui(&mut self, ui: &mut Ui, _tile_id: TileId, pane: &mut Pane) -> UiResponse {
210 panes::show(*pane, ui, self.st);
211 UiResponse::None
212 }
213
214 fn tab_title_for_pane(&mut self, pane: &Pane) -> WidgetText {
215 pane.title().into()
216 }
217
218 fn is_tab_closable(&self, tiles: &Tiles<Pane>, tile_id: TileId) -> bool {
219 matches!(tiles.get(tile_id), Some(Tile::Pane(p)) if *p != Pane::Editor)
220 }
221
222 fn tab_ui(
223 &mut self,
224 tiles: &mut Tiles<Pane>,
225 ui: &mut Ui,
226 id: Id,
227 tile_id: TileId,
228 state: &TabState,
229 ) -> Response {
230 let p = palette(ui.ctx());
231 let title = match tiles.get(tile_id) {
232 Some(Tile::Pane(pane)) => pane.title(),
233 _ => "Group",
234 };
235 let galley = WidgetText::from(p.label(title)).into_galley(
236 ui,
237 Some(egui::TextWrapMode::Extend),
238 f32::INFINITY,
239 egui::TextStyle::Small,
240 );
241 let pad = 12.0;
242 let close_w = if state.closable { 16.0 } else { 0.0 };
243 let width = galley.size().x + 2.0 * pad + close_w;
244 let (_, rect) = ui.allocate_space(vec2(width, ui.available_height()));
245 let resp = ui
246 .interact(rect, id, Sense::click_and_drag())
247 .on_hover_cursor(egui::CursorIcon::PointingHand);
248
249 if ui.is_rect_visible(rect) && !state.is_being_dragged {
250 let fg = if state.active {
251 p.text_display
252 } else if resp.hovered() {
253 p.text_secondary
254 } else {
255 p.text_disabled
256 };
257 let text_rect = Rect::from_min_max(
258 rect.min + vec2(pad, 0.0),
259 rect.max - vec2(pad + close_w, 0.0),
260 );
261 let pos = Align2::LEFT_CENTER
262 .align_size_within_rect(galley.size(), text_rect)
263 .min;
264 let text_w = galley.size().x;
265 ui.painter().galley(pos, galley, fg);
266 if state.active {
267 ui.painter().hline(
268 pos.x..=pos.x + text_w,
269 rect.bottom() - 1.0,
270 Stroke::new(2.0, p.text_display),
271 );
272 }
273 if state.closable {
274 let c = Rect::from_center_size(
275 egui::pos2(rect.right() - pad - 4.0, rect.center().y),
276 Vec2::splat(12.0),
277 );
278 let close = ui
279 .interact(c, id.with("close"), Sense::click())
280 .on_hover_cursor(egui::CursorIcon::PointingHand);
281 if resp.hovered() || close.hovered() || state.active {
282 let col = if close.hovered() {
283 p.text_display
284 } else {
285 p.text_disabled
286 };
287 let r = c.shrink(3.0);
288 let s = Stroke::new(1.2, col);
289 ui.painter()
290 .line_segment([r.left_top(), r.right_bottom()], s);
291 ui.painter()
292 .line_segment([r.right_top(), r.left_bottom()], s);
293 }
294 if (close.clicked() || resp.clicked_by(egui::PointerButton::Middle))
295 && self.on_tab_close(tiles, tile_id)
296 {
297 tiles.remove(tile_id);
298 }
299 }
300 }
301 resp
302 }
303
304 fn is_tab_bar_visible(&self, _tile_id: TileId, tabs: &Tabs) -> bool {
305 // The editor brings its own document tabs; a second bar above them
306 // would only repeat "EDITOR".
307 !(tabs.children.len() == 1 && self.editor_tile == Some(tabs.children[0]))
308 }
309
310 fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
311 TAB_BAR
312 }
313
314 fn gap_width(&self, _style: &egui::Style) -> f32 {
315 GAP
316 }
317
318 fn min_size(&self) -> f32 {
319 64.0
320 }
321
322 fn tab_bar_color(&self, _visuals: &egui::Visuals) -> Color32 {
323 Color32::TRANSPARENT
324 }
325
326 fn tab_bar_hline_stroke(&self, visuals: &egui::Visuals) -> Stroke {
327 let p = if visuals.dark_mode {
328 &theme::DARK
329 } else {
330 &theme::LIGHT
331 };
332 Stroke::new(1.0, p.border)
333 }
334
335 fn resize_stroke(&self, style: &egui::Style, state: ResizeState) -> Stroke {
336 let p = if style.visuals.dark_mode {
337 &theme::DARK
338 } else {
339 &theme::LIGHT
340 };
341 match state {
342 ResizeState::Idle => Stroke::NONE,
343 ResizeState::Hovering => Stroke::new(1.0, p.text_disabled),
344 ResizeState::Dragging => Stroke::new(1.0, p.text_display),
345 }
346 }
347
348 fn simplification_options(&self) -> SimplificationOptions {
349 SimplificationOptions {
350 all_panes_must_have_tabs: true,
351 ..Default::default()
352 }
353 }
354
355 fn drag_preview_color(&self, visuals: &egui::Visuals) -> Color32 {
356 visuals.selection.bg_fill
357 }
358
359 fn drag_preview_stroke(&self, visuals: &egui::Visuals) -> Stroke {
360 visuals.selection.stroke
361 }
362}