Files
ElyOffice/apps/ely-show/src/output/dialog.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

138 lines
5.4 KiB
Rust

//! The output dialog: what, which slides, how many a page.
use ely_gpui_component::{
buttons::{Button, ButtonVariant, SegmentedControl},
data_display::DescriptionList,
forms::{Choice, NumberInput, Select},
overlays::Dialog as Card,
theme::ControlSize,
};
use gpui::{ParentElement as _, Styled as _, Window, div};
use super::{Kind, Output, Range};
use crate::deck::Deck;
use crate::dialog::Dialog;
/// What goes out, which slides, how many a page.
pub fn output_dialog(
this: gpui::Entity<Deck>,
output: &Output,
dismiss: impl Fn(&mut Window, &mut gpui::App) + 'static,
cx: &gpui::App,
) -> Card {
let text = |key: &str| Deck::text(cx, key, &[]);
let change = |this: &gpui::Entity<Deck>, cx: &mut gpui::App, edit: &dyn Fn(&mut Output)| {
this.update(cx, |deck, cx| {
if let Some(Dialog::Output(output)) = &mut deck.dialog {
edit(output);
}
cx.notify();
})
};
let kinds: &[(Kind, &str)] = match output.print {
true => &[(Kind::Slides, "slides"), (Kind::Handouts, "handouts")],
false => &[(Kind::Slides, "pdf"), (Kind::Pictures, "pictures"), (Kind::Handouts, "handouts")],
};
let now = kinds.iter().find(|(kind, _)| *kind == output.kind).map_or("pdf", |(_, id)| id);
let kind = {
let (this, kinds) = (this.clone(), kinds.to_vec());
kinds
.iter()
.fold(SegmentedControl::new("show-output-kind", now), |control, (_, id)| {
control.segment(*id, text(&format!("show.output.{id}")), None)
})
.size(ControlSize::Sm)
.on_change(move |value, _, cx| {
if let Some((kind, _)) = kinds.iter().find(|(_, id)| *id == value.as_ref()) {
let kind = *kind;
change(&this, cx, &|output| output.kind = kind);
}
})
};
let which = match output.range {
Range::All => "all",
Range::Current => "current",
Range::Slides(..) => "range",
};
let last = output.hidden.len() as u32;
let range = {
let this = this.clone();
let control = SegmentedControl::new("show-output-range", which)
.size(ControlSize::Sm)
.segment("all", text("show.output.all"), None);
// Master view has no current slide.
let control = match output.current {
Some(_) => control.segment("current", text("show.output.current"), None),
None => control,
};
control
.segment("range", text("show.output.range"), None)
.on_change(move |value, _, cx| {
let range = match value.as_ref() {
"all" => Range::All,
"current" => Range::Current,
_ => Range::Slides(1, last),
};
change(&this, cx, &|output| output.range = range);
})
};
let bound = |id: &'static str, value: u32, first: bool| {
// A typed number counts before blur commits it.
let set = move |this: &gpui::Entity<Deck>, value: f64, cx: &mut gpui::App| {
let value = value.round() as u32;
change(this, cx, &|output| {
if let Range::Slides(from, to) = &mut output.range {
*(if first { from } else { to }) = value;
}
})
};
let (typed, committed) = (this.clone(), this.clone());
NumberInput::new(id, f64::from(value))
.label(text(if first { "show.output.from" } else { "show.output.to" }))
.range(1.0, f64::from(last))
.size(ControlSize::Sm)
.on_change(move |value, _, cx| set(&typed, value, cx))
.on_commit(move |value, _, cx| set(&committed, value, cx))
};
let bounds = match output.range {
Range::Slides(from, to) => Some(
div()
.flex()
.gap_2()
.child(div().w_full().max_w_24().child(bound("show-output-from", from, true)))
.child(div().w_full().max_w_24().child(bound("show-output-to", to, false))),
),
_ => None,
};
let per = (output.kind == Kind::Handouts).then(|| {
let this = this.clone();
let choices = [1u8, 2, 3, 4, 6, 9].map(|n| Choice::new(n.to_string(), n.to_string()));
Select::new("show-output-per", choices)
.label(text("show.output.per"))
.selected(output.per.to_string())
.on_change(move |value, _, cx| {
let per: u8 = value.parse().expect("listed numbers");
change(&this, cx, &|output| output.per = per);
})
});
let mut rows = DescriptionList::new()
.item(text("show.output.what"), kind)
.item(text("show.output.which"), div().flex().flex_col().gap_2().child(range).children(bounds));
if let Some(per) = per {
rows = rows.item(text("show.output.per"), per);
}
let (title, go) = match output.print {
true => ("show.output.print_title", "show.output.print"),
false => ("show.output.export_title", "show.output.export"),
};
let go = text(go);
Card::new("show-output", text(title), dismiss)
.child(rows)
.action(ely_office::cancel(cx))
.action(move |_| {
Button::new("show-output-go", go)
.variant(ButtonVariant::Primary)
.on_click(move |_, _, cx| this.update(cx, |deck, cx| deck.output(cx)))
})
}