Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions crates/app/src/shot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ enum Op {
RegionData,
XpsSetup,
CraftSetup,
PanelControls(bool),
XpsTab(plotx_core::state::XpsWorkbenchTab),
/// Show a Ribbon task tab. Sets the state directly (like [`Op::XpsTab`])
/// so the capture shows the tab's command row without the side effects a
Expand Down Expand Up @@ -197,6 +198,18 @@ const SCENES: &[Scene] = &[
shot(8, "xps_diagnostics"),
act(2, Op::CraftSetup),
shot(8, "craft_results"),
act(2, Op::PanelControls(false)),
shot(8, "panel_controls_collapsed"),
act(2, Op::Resize(720.0, 700.0)),
shot(8, "panel_controls_narrow"),
act(2, Op::Resize(1440.0, 900.0)),
act(2, Op::PanelControls(true)),
shot(8, "panel_controls_expanded"),
Scene {
settle: 4,
op: None,
shot: None,
},
];

pub struct ShotDriver {
Expand Down Expand Up @@ -379,6 +392,10 @@ fn run_op(op: Op, app: &mut PlotxApp, ctx: &egui::Context) -> Result<(), String>
}
Op::XpsSetup => xps_setup(app, ctx)?,
Op::CraftSetup => craft_shot::setup(app, ctx)?,
Op::PanelControls(expanded) => {
app.session.ui.ribbon_expanded = expanded;
app.session.ui.craft_task_collapsed = !expanded;
}
Op::XpsTab(tab) => app.session.ui.xps_workbench_tab = tab,
Op::RibbonTab(tab) => app.session.ui.ribbon_tab = tab,
Op::Zoom(factor) => ctx.set_zoom_factor(factor),
Expand Down Expand Up @@ -773,24 +790,5 @@ fn save_png(path: &Path, image: &egui::ColorImage) -> Result<(), String> {
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn automated_exit_bypasses_dirty_project_prompt() {
let mut app = PlotxApp::new_with_settings(plotx_core::settings::Settings::default());
app.mark_document_dirty();
let ctx = egui::Context::default();

let output = ctx.run_ui(egui::RawInput::default(), |ui| {
request_exit(&mut app, ui.ctx());
});

assert!(app.session.allow_close);
let root = output
.viewport_output
.get(&egui::ViewportId::ROOT)
.expect("root viewport output");
assert!(root.commands.contains(&egui::ViewportCommand::Close));
}
}
#[path = "shot/tests.rs"]
mod tests;
19 changes: 19 additions & 0 deletions crates/app/src/shot/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use super::*;

#[test]
fn automated_exit_bypasses_dirty_project_prompt() {
let mut app = PlotxApp::new_with_settings(plotx_core::settings::Settings::default());
app.mark_document_dirty();
let ctx = egui::Context::default();

let output = ctx.run_ui(egui::RawInput::default(), |ui| {
request_exit(&mut app, ui.ctx());
});

assert!(app.session.allow_close);
let root = output
.viewport_output
.get(&egui::ViewportId::ROOT)
.expect("root viewport output");
assert!(root.commands.contains(&egui::ViewportCommand::Close));
}
1 change: 1 addition & 0 deletions crates/app/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod menus;
#[cfg(target_os = "macos")]
pub(crate) mod native_menu;
mod object_inspector;
mod panel_chrome;
mod present;
mod primary_sidebar;
pub(crate) mod processing_templates;
Expand Down
75 changes: 75 additions & 0 deletions crates/app/src/ui/panel_chrome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use egui::{Color32, Rect, Response, Sense, Stroke, StrokeKind, Ui, vec2};

pub(super) const BUTTON_WIDTH: f32 = 30.0;

#[derive(Clone, Copy)]
pub(super) enum Edge {
Left,
Right,
Top,
Bottom,
}

pub(super) fn toggle(ui: &mut Ui, edge: Edge, visible: bool, label: &str) -> Response {
let (rect, response) = button(ui, label);
let color = if visible {
ui.style().interact(&response).text_color()
} else {
ui.visuals().weak_text_color()
};
paint_glyph(ui, rect, edge, visible, color);
response.on_hover_text(label)
}

pub(super) fn close(ui: &mut Ui, label: &str) -> Response {
ui.add_sized(
vec2(BUTTON_WIDTH, ui.spacing().interact_size.y),
egui::Button::new(egui_phosphor::regular::X).frame_when_inactive(false),
)
.on_hover_text(label)
}

fn button(ui: &mut Ui, label: &str) -> (Rect, Response) {
let (rect, response) = ui.allocate_exact_size(
vec2(BUTTON_WIDTH, ui.spacing().interact_size.y),
Sense::click(),
);
response.widget_info(|| {
egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), label)
});
let visuals = ui.style().interact(&response);
if response.hovered() || response.is_pointer_button_down_on() || response.has_focus() {
ui.painter()
.rect_filled(rect, visuals.corner_radius, visuals.weak_bg_fill);
}
(rect, response)
}

fn paint_glyph(ui: &Ui, rect: Rect, edge: Edge, filled: bool, color: Color32) {
let painter = ui.painter();
let outer = Rect::from_center_size(rect.center(), vec2(16.0, 12.0));
painter.rect_stroke(outer, 3.0, Stroke::new(1.2_f32, color), StrokeKind::Inside);
let inner = outer.shrink(2.0);
let band = match edge {
Edge::Left => Rect::from_min_size(inner.min, vec2(5.0, inner.height())),
Edge::Right => Rect::from_min_size(
inner.right_top() - vec2(5.0, 0.0),
vec2(5.0, inner.height()),
),
Edge::Top => Rect::from_min_size(inner.min, vec2(inner.width(), 3.0)),
Edge::Bottom => Rect::from_min_size(
inner.left_bottom() - vec2(0.0, 3.0),
vec2(inner.width(), 3.0),
),
};
if filled {
painter.rect_filled(band, 1.5, color);
} else {
painter.rect_stroke(band, 1.5, Stroke::new(1.0_f32, color), StrokeKind::Inside);
}
}

pub(super) fn collapse(ui: &mut Ui, collapsed: bool, name: &str) -> Response {
let action = if collapsed { "Expand" } else { "Collapse" };
toggle(ui, Edge::Bottom, !collapsed, &format!("{action} {name}"))
}
22 changes: 8 additions & 14 deletions crates/app/src/ui/ribbon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,22 +177,16 @@ fn render_chrome_controls(
ui: &mut Ui,
compact_controls: bool,
) {
let collapse_label = if compact_controls {
let collapse = super::panel_chrome::toggle(
ui,
super::panel_chrome::Edge::Top,
app.session.ui.ribbon_expanded,
if app.session.ui.ribbon_expanded {
icon::CARET_UP.to_owned()
"Collapse ribbon"
} else {
icon::CARET_DOWN.to_owned()
}
} else if app.session.ui.ribbon_expanded {
format!("{} Collapse ribbon", icon::CARET_UP)
} else {
format!("{} Expand ribbon", icon::CARET_DOWN)
};
// The strip next to the task tabs stays quiet: chrome buttons show
// their frame only on hover so they read no heavier than the tabs.
let collapse = ui
.add(Button::new(collapse_label).frame_when_inactive(false))
.on_hover_text("Collapse or expand the ribbon command area");
"Expand ribbon"
},
);
if collapse.clicked() {
app.session.ui.ribbon_expanded = !app.session.ui.ribbon_expanded;
}
Expand Down
77 changes: 9 additions & 68 deletions crates/app/src/ui/ribbon_chrome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,6 @@ fn task_tabs_width(ui: &Ui) -> f32 {
fn controls_width(app: &PlotxApp, ui: &Ui, compact: bool) -> f32 {
use plotx_core::update::UpdateStatus;

let collapse = if compact {
icon::CARET_UP.to_owned()
} else if app.session.ui.ribbon_expanded {
format!("{} Collapse ribbon", icon::CARET_UP)
} else {
format!("{} Expand ribbon", icon::CARET_DOWN)
};
let search = if compact {
icon::MAGNIFYING_GLASS.to_owned()
} else {
Expand All @@ -135,21 +128,21 @@ fn controls_width(app: &PlotxApp, ui: &Ui, compact: bool) -> f32 {
_ => String::new(),
};
let spacing = CONTROL_SPACING;
[collapse, search, update]
[search, update]
.into_iter()
.filter(|text| !text.is_empty())
.map(|text| text_width(ui, text, TextStyle::Button))
.sum::<f32>()
// The two sidebar layout toggles, the separator before them, and
// The two sidebar toggles, Ribbon toggle, separator, and
// their share of the item spacing.
+ 2.0 * SIDEBAR_TOGGLE_WIDTH
+ 3.0 * SIDEBAR_TOGGLE_WIDTH
+ 6.0
+ 5.0 * spacing
}

/// Fixed width of one sidebar layout toggle; shared with the width estimate
/// in `controls_width` so compaction accounts for the pair.
pub(super) const SIDEBAR_TOGGLE_WIDTH: f32 = 30.0;
pub(super) const SIDEBAR_TOGGLE_WIDTH: f32 = super::panel_chrome::BUTTON_WIDTH;

/// Fixed gap between task tabs. Command density applies only below this row,
/// so switching tabs cannot move the task buttons or trailing chrome.
Expand All @@ -171,69 +164,17 @@ pub(super) fn sidebar_toggle_button(
use super::commands;

let command = commands::describe(app, id);
let sidebar_visible = command.checked == Some(true);
let (rect, response) = ui.allocate_exact_size(
egui::vec2(SIDEBAR_TOGGLE_WIDTH, ui.spacing().interact_size.y),
egui::Sense::click(),
);
if response.clicked() {
commands::execute(id, app, clipboard, ui.ctx());
}
let visuals = ui.style().interact(&response);
if response.hovered() || response.is_pointer_button_down_on() {
// Match the neighbouring frameless chrome buttons: a quiet fill that
// appears only under the pointer.
ui.painter()
.rect_filled(rect, visuals.corner_radius, visuals.weak_bg_fill);
}
let color = if sidebar_visible {
visuals.text_color()
} else {
ui.visuals().weak_text_color()
};
paint_sidebar_glyph(
ui,
rect,
id == commands::CommandId::TogglePrimarySidebar,
sidebar_visible,
color,
);
let tip = match &command.shortcut {
Some(shortcut) => format!("{} ({shortcut})", command.label),
None => command.label.clone(),
};
response.on_hover_text(tip);
}

fn paint_sidebar_glyph(ui: &Ui, rect: egui::Rect, left: bool, filled: bool, color: egui::Color32) {
let painter = ui.painter();
let outer = egui::Rect::from_center_size(rect.center(), egui::vec2(16.0, 12.0));
painter.rect_stroke(
outer,
3.0,
egui::Stroke::new(1.2_f32, color),
egui::StrokeKind::Inside,
);
let band = if left {
egui::Rect::from_min_max(
outer.min + egui::vec2(2.0, 2.0),
egui::pos2(outer.min.x + 7.0, outer.max.y - 2.0),
)
let edge = if id == commands::CommandId::TogglePrimarySidebar {
super::panel_chrome::Edge::Left
} else {
egui::Rect::from_min_max(
egui::pos2(outer.max.x - 7.0, outer.min.y + 2.0),
outer.max - egui::vec2(2.0, 2.0),
)
super::panel_chrome::Edge::Right
};
if filled {
painter.rect_filled(band, 1.5, color);
} else {
painter.rect_stroke(
band,
1.5,
egui::Stroke::new(1.0_f32, color),
egui::StrokeKind::Inside,
);
if super::panel_chrome::toggle(ui, edge, command.checked == Some(true), &tip).clicked() {
commands::execute(id, app, clipboard, ui.ctx());
}
}

Expand Down
22 changes: 2 additions & 20 deletions crates/app/src/ui/tools/craft.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use egui::{Button, Ui};
use egui_phosphor::regular as icon;
use plotx_core::state::{
CraftAnalysisIntent, CraftTaskPage, Dataset, FrameRef, PlotxApp, Selection, TaskDockTab, Tool,
};
Expand Down Expand Up @@ -207,27 +206,10 @@ pub(crate) fn render_task(app: &mut PlotxApp, host: &mut Ui) {
ui.set_width(width);
crate::ui::card_frame(dark, egui::Margin::ZERO).show(ui, |ui| {
task_card::header(ui, area_id, "CRAFT", None::<&str>, |ui| {
if ui
.small_button(icon::X)
.on_hover_text("Close CRAFT")
.clicked()
{
if crate::ui::panel_chrome::close(ui, "Close CRAFT").clicked() {
close = true;
}
let glyph = if collapsed {
icon::CARET_DOWN
} else {
icon::CARET_UP
};
if ui
.small_button(glyph)
.on_hover_text(if collapsed {
"Expand CRAFT"
} else {
"Collapse CRAFT"
})
.clicked()
{
if crate::ui::panel_chrome::collapse(ui, collapsed, "CRAFT").clicked() {
toggle_collapse = true;
}
});
Expand Down
21 changes: 2 additions & 19 deletions crates/app/src/ui/tools/curve_fit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,27 +82,10 @@ pub(crate) fn render_task(app: &mut PlotxApp, host: &mut Ui) {
"Curve Fit",
Some(format!("{curve_count} · {points} points each")),
|ui| {
if ui
.small_button(icon::X)
.on_hover_text("Close Curve Fit")
.clicked()
{
if crate::ui::panel_chrome::close(ui, "Close Curve Fit").clicked() {
close = true;
}
let glyph = if collapsed {
icon::CARET_DOWN
} else {
icon::CARET_UP
};
if ui
.small_button(glyph)
.on_hover_text(if collapsed {
"Expand Curve Fit"
} else {
"Collapse Curve Fit"
})
.clicked()
{
if crate::ui::panel_chrome::collapse(ui, collapsed, "Curve Fit").clicked() {
toggle_collapse = true;
}
},
Expand Down
Loading
Loading