Workspace
Group: Chrome
Code
const INITIAL: [(&str, &str); 3] = [
("gallery:doc:alpha", "alpha"),
("gallery:doc:beta", "beta"),
("gallery:doc:gamma", "gamma"),
];
const BACKLOG: [(&str, &str); 2] = [
("gallery:doc:delta", "delta-doc"),
("gallery:doc:epsilon", "epsilon-doc"),
];
const BAR_ID: &str = "gallery:workspace:tabs";
const VARIANT_COUNT: usize = 2;
/// Build variant `variant`'s initial controller: variant 1 opens one
/// extra preview tab on top of the three permanent docs variant 0 has.
fn build_workspace(variant: usize) -> WorkspaceController {
let mut workspace = WorkspaceController::new(BAR_ID);
for (id, label) in INITIAL {
workspace.open(WorkspaceDoc::new(id, label));
}
if variant == 1 {
let (id, label) = BACKLOG[0];
workspace.open_preview(WorkspaceDoc::new(id, label));
} else {
workspace.activate(INITIAL[0].0);
}
workspace
}
pub struct WorkspaceDemo {
// `RefCell` so `render(&self, …)` can call the controller's `&mut
// self` render — each variant gets its own instance and counters so
// switching variants doesn't share state.
workspaces: [RefCell<WorkspaceController>; VARIANT_COUNT],
next_backlog: [usize; VARIANT_COUNT],
next_preview_backlog: [usize; VARIANT_COUNT],
last_event: String,
}
impl WorkspaceDemo {
pub fn new() -> Self {
Self {
workspaces: [
RefCell::new(build_workspace(0)),
RefCell::new(build_workspace(1)),
],
next_backlog: [0; VARIANT_COUNT],
next_preview_backlog: [0, 1],
last_event: "ready".into(),
}
}
fn describe(event: &WorkspaceEvent) -> String {
match event {
WorkspaceEvent::Opened { id, .. } => format!("opened {id}"),
WorkspaceEvent::Activated { id, .. } => format!("activated {id}"),
WorkspaceEvent::Closed { id, .. } => format!("closed {id}"),
WorkspaceEvent::Reordered { id, from, to } => format!("reordered {id} {from}->{to}"),
WorkspaceEvent::Promoted { id } => format!("promoted {id}"),
_ => "unknown workspace event".to_string(),
}
}
fn record(&mut self, events: &[WorkspaceEvent]) -> Reaction {
if events.is_empty() {
return Reaction::Continue;
}
self.last_event = events
.iter()
.map(Self::describe)
.collect::<Vec<_>>()
.join(", ");
Reaction::Redraw
}
fn open_next_backlog_doc(&mut self, variant: usize) -> Reaction {
let Some((id, label)) = BACKLOG.get(self.next_backlog[variant]).copied() else {
self.last_event = "backlog exhausted".into();
return Reaction::Redraw;
};
self.next_backlog[variant] += 1;
let events = self.workspaces[variant]
.borrow_mut()
.open(WorkspaceDoc::new(id, label));
self.record(&events)
}
fn open_next_preview_doc(&mut self, variant: usize) -> Reaction {
let Some((id, label)) = BACKLOG.get(self.next_preview_backlog[variant]).copied() else {
self.last_event = "preview backlog exhausted".into();
return Reaction::Redraw;
};
self.next_preview_backlog[variant] += 1;
let events = self.workspaces[variant]
.borrow_mut()
.open_preview(WorkspaceDoc::new(id, label));
self.record(&events)
}
fn promote_active(&mut self, variant: usize) -> Reaction {
let mut ws = self.workspaces[variant].borrow_mut();
let Some(id) = ws.active_id().map(str::to_string) else {
return Reaction::Continue;
};
match ws.promote(&id) {
Some(ev) => {
drop(ws);
self.record(&[ev])
}
None => Reaction::Continue,
}
}
fn label_bar(id: &str, text: String, fg: Color) -> StatusBar {
StatusBar {
id: WidgetId::new(id),
left_segments: vec![StatusBarSegment {
text,
fg,
bg: Color::rgb(30, 30, 30),
bold: false,
action_id: None,
}],
right_segments: vec![],
}
}
}
impl Default for WorkspaceDemo {
fn default() -> Self {
Self::new()
}
}
impl Demo for WorkspaceDemo {
fn name(&self) -> &'static str {
"Workspace"
}
fn group(&self) -> &'static str {
"Chrome"
}
fn variants(&self) -> &'static [&'static str] {
&["Permanent tabs", "With preview tab"]
}
fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
let lh = backend.line_height();
let strip_h = lh;
let strip = Rect::new(area.x, area.y, area.width, strip_h);
self.workspaces[variant].borrow_mut().render(backend, strip);
let body_rect = Rect::new(area.x, area.y + strip_h, area.width, lh);
let ws = self.workspaces[variant].borrow();
let body = match ws.active_id() {
Some(id) if ws.is_preview(id) => format!(" viewing: {id} (preview) "),
Some(id) => format!(" viewing: {id} "),
None => " viewing: (no documents open) ".to_string(),
};
backend.draw_status_bar_interactive(
body_rect,
&Self::label_bar("gallery:workspace:body", body, Color::rgb(220, 220, 220)),
&InteractionState::new(),
);
let hint_rect = Rect::new(area.x, area.y + strip_h + lh, area.width, lh);
backend.draw_status_bar_interactive(
hint_rect,
&Self::label_bar(
"gallery:workspace:hint",
format!(" last: {} ", self.last_event),
Color::rgb(150, 200, 150),
),
&InteractionState::new(),
);
}
fn handle(
&mut self,
variant: usize,
event: &UiEvent,
_backend: &mut dyn Backend,
_area: Rect,
) -> Reaction {
match event {
UiEvent::KeyPressed {
key: Key::Char('o'),
..
} => self.open_next_backlog_doc(variant),
UiEvent::KeyPressed {
key: Key::Char('p'),
..
} => self.open_next_preview_doc(variant),
UiEvent::KeyPressed {
key: Key::Char('m'),
..
} => self.promote_active(variant),
UiEvent::KeyPressed { key, modifiers, .. } => {
let handled = self.workspaces[variant]
.borrow_mut()
.handle_key(key, *modifiers);
match handled {
Some(ev) => self.record(&[ev]),
None => Reaction::Continue,
}
}
UiEvent::MouseDown {
position, button, ..
} => {
let events = self.workspaces[variant]
.borrow_mut()
.handle_click(position.x, position.y, *button);
self.record(&events)
}
_ => Reaction::Continue,
}
}
fn source(&self) -> &'static str {
extract_region(SOURCE)
}
fn data(&self, variant: usize) -> serde_json::Value {
let ws = self.workspaces[variant].borrow();
serde_json::json!({
"docs": ws.docs().iter().map(|d| &d.label).collect::<Vec<_>>(),
"active": ws.active_id(),
"last_event": self.last_event,
})
}
fn caps_note(&self, _variant: usize, _caps: &BackendCaps) -> Option<String> {
None
}
}
Backends
| Variant | TUI | GTK | macOS | Windows |
|---|---|---|---|---|
| Permanent tabs | ||||
| With preview tab |