mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-28 20:47:15 -06:00
Refactor ui-egui for strict Clippy checks
This commit is contained in:
@@ -2,7 +2,13 @@ use super::clock::{monotonic_secs, now_ms};
|
||||
use super::ime_hooks::ime_in_ui_pass_key;
|
||||
use super::services::{InboxEvent, Services};
|
||||
use super::state::{FilePlace, FilePurpose, ImeDescriptorKey, ImeDestination, ImeTerminalStamp, LocatedPlace, RECENT_MAX, UiState, ViewMode};
|
||||
use super::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::updates_ui;
|
||||
use super::{
|
||||
AnimPreview, ControlRequest, FileTarget, HashMap, ImeCancelled, ImeComposition, ImeResetPhase, ImeTerminalPending, InputTarget, Mods,
|
||||
PendingFile, Perf, Receiver, Session, SlideApp, Target, UI_COMMANDS, UiRequest, Value, backstage, canvas, capture, dialogs, doc_window,
|
||||
eyedropper, json, media, menus, notes, panes, preview, qat, ribbon, selpane, show, sorter, status, textures, theme, thumbs, toast, uifonts,
|
||||
};
|
||||
|
||||
mod clipboard;
|
||||
mod commands;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{SlideApp, Value, json};
|
||||
|
||||
/// Text put on the system clipboard when the copied objects (a rectangle, a picture, slides…)
|
||||
/// carry no text. egui-winit drops Ctrl+V when the system clipboard is empty, so without it the
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::updates_ui;
|
||||
use super::{SlideApp, UI_COMMANDS, Value, ViewMode, backstage, dialogs, doc_window, json, media, qat, theme};
|
||||
|
||||
fn grid_options_touched(p: &Value) -> bool {
|
||||
["spacing", "snap", "smartGuides", "gridlines", "guides"].iter().any(|k| p.get(*k).is_some())
|
||||
@@ -266,7 +268,7 @@ impl SlideApp {
|
||||
self.dialog = Some(dialogs::Dialog::new(d));
|
||||
}
|
||||
"app.links" => {
|
||||
use deckcraft_engine::links::*;
|
||||
use deckcraft_engine::links::{APP_PAGE, APPS, DISCORD, GITHUB, WEBSITE};
|
||||
return Ok(json!({"discord": DISCORD, "website": WEBSITE, "apps": APPS, "appPage": APP_PAGE, "github": GITHUB}));
|
||||
}
|
||||
_ => return Err(format!("unknown UI command `{id}`")),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{SlideApp, json};
|
||||
use crate::control;
|
||||
|
||||
impl SlideApp {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
FilePlace, FilePurpose, FileTarget, InboxEvent, LocatedPlace, PendingFile, RECENT_MAX, SlideApp, Target, UiRequest, Value, ViewMode, dialogs,
|
||||
json, media,
|
||||
};
|
||||
|
||||
fn purpose_of(purpose: &str) -> FilePurpose {
|
||||
match purpose {
|
||||
@@ -446,7 +449,7 @@ impl SlideApp {
|
||||
let owner = if self.show.is_some() { media::Owner::Show } else { media::Owner::Editor };
|
||||
// The command resolved `shape` on the editor's slide, also while a show runs.
|
||||
if let Some((uid, doc, slide)) = self.session.active().map(|d| (d.uid, d.doc.clone(), d.selection.slide)) {
|
||||
self.media.request(uid, &doc, slide, &action, shape, ms, owner);
|
||||
self.media.request(uid, &doc, slide, &action, (shape, ms), owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{SlideApp, json, media, monotonic_secs, theme};
|
||||
|
||||
impl SlideApp {
|
||||
/// Style, fonts and zoom setup. Runs in a real UI pass, not from `run_logic`, because
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
ImeCancelled, ImeComposition, ImeDescriptorKey, ImeDestination, ImeResetPhase, ImeTerminalPending, ImeTerminalStamp, SlideApp, Target, Value,
|
||||
ViewMode, ime_in_ui_pass_key,
|
||||
};
|
||||
|
||||
impl SlideApp {
|
||||
/// Borrow faces for interface text in scripts the interface fonts lack (#101): the open
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{InputTarget, SlideApp, show};
|
||||
|
||||
impl SlideApp {
|
||||
/// What a queued synthetic event is meant for. The control protocol hands out window
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::clipboard::OBJECTS_MARKER;
|
||||
use super::*;
|
||||
use super::{Mods, SlideApp, Value, backstage, json, menus};
|
||||
|
||||
impl SlideApp {
|
||||
/// The app shortcuts that still make sense on the empty File/start screen: create, open,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{AnimPreview, SlideApp, Value, ViewMode, json, media, preview, show};
|
||||
|
||||
impl SlideApp {
|
||||
/// Whether ⌘A should act on the slide list: always in the sorter, in Normal view once the
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, Backstage, CONTENT_W, Color32, CornerRadius, Icon, Outcome, Page, Rect, RichText, Sense, SlideApp, Stroke, StrokeKind, Tokens, Ui, Value,
|
||||
arrow_link, dim, heading, icons, json, pos2, search, theme, vec2,
|
||||
};
|
||||
|
||||
/// A new-presentation tile: a theme preview drawn in code (or a blank slide) over its name.
|
||||
fn tile(ui: &mut Ui, label: &str, theme: Option<&deckcraft_model::Theme>, big: bool) -> egui::Response {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::files::task;
|
||||
use super::*;
|
||||
use super::{Backstage, Icon, Outcome, Page, RichText, Sense, SlideApp, Tokens, Ui, Value, dim, heading, json, theme, vec2};
|
||||
|
||||
/// Save As and Change File Type: (id for [`SlideApp::export_as`], label, detail).
|
||||
pub const SAVE_TYPES: [(&str, &str, &str); 7] = [
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::files::{place_row, square_button};
|
||||
use super::info::SAVE_TYPES;
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, Backstage, Color32, CornerRadius, ExportTask, Icon, PrintScope, Rect, RichText, Sense, SlideApp, Stroke, StrokeKind, Tokens, Ui, Value,
|
||||
bullets, dim, heading, icon_button, json, pos2, theme, vec2,
|
||||
};
|
||||
|
||||
/// The slides the Print page prints, in order (0-based).
|
||||
pub fn print_slides(doc: &deckcraft_model::Presentation, bs: &Backstage, current: usize) -> Result<Vec<usize>, String> {
|
||||
|
||||
@@ -83,6 +83,69 @@ pub fn layout(app: &SlideApp, avail: Rect) -> (Rect, f32) {
|
||||
(r, scale)
|
||||
}
|
||||
|
||||
/// The mouse wheel or a trackpad flick turns slides when the canvas has nothing to scroll.
|
||||
fn wheel_navigation(app: &mut SlideApp, ui: &mut Ui, carry_id: egui::Id, doc_uid: u64) {
|
||||
if app.anim_preview_active() || app.session.tool.marquee.is_some() || app.session.tool.dragging() {
|
||||
return;
|
||||
}
|
||||
let (_, mut carry): (u64, f32) = ui.data_mut(|d| d.get_temp(carry_id).unwrap_or_default());
|
||||
let wheel_step = app.session.active().and_then(|d| {
|
||||
let current = d.selection.slide;
|
||||
ui.input(|i| wheel_slide_target(&i.events, current, d.doc.slides.len(), &mut carry)).map(|target| (current, target))
|
||||
});
|
||||
ui.data_mut(|d| d.insert_temp(carry_id, (doc_uid, carry)));
|
||||
if let Some((current, index)) = wheel_step {
|
||||
if index != current {
|
||||
let _ = app.run("slide.go", json!({"index": index}));
|
||||
}
|
||||
} else if let Some(next) = page_flick(ui.input(|i| trackpad_scroll(&i.events))) {
|
||||
// Point-based trackpad gestures keep their cooldown. Raw input avoids reading
|
||||
// the smoothed tail of a discrete mouse notch as another page turn.
|
||||
let gate = ui.scope_id().with("wheel_gate");
|
||||
let last: f64 = ui.data_mut(|d| d.get_temp(gate).unwrap_or(f64::NEG_INFINITY));
|
||||
let now = ui.input(|i| i.time);
|
||||
if now - last > 0.35 {
|
||||
ui.data_mut(|d| d.insert_temp(gate, now));
|
||||
let _ = app.run(if next { "slide.next" } else { "slide.previous" }, json!({}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_deck_prompt(app: &mut SlideApp, ui: &mut Ui, avail: Rect, t: &Tokens) {
|
||||
let resp = ui.allocate_rect(avail, Sense::click());
|
||||
ui.painter().text(avail.center(), Align2::CENTER_CENTER, "Click to add first slide", theme::font(22.0), t.text_dim);
|
||||
if resp.clicked() {
|
||||
let _ = app.run("slide.new", json!({"layout": "title"}));
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_wheel_carry(ui: &Ui, carry_id: egui::Id, doc_uid: u64) {
|
||||
ui.data_mut(|d| {
|
||||
if d.get_temp::<(u64, f32)>(carry_id).is_none_or(|(uid, _)| uid != doc_uid) {
|
||||
d.insert_temp(carry_id, (doc_uid, 0.0_f32));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The scroll offset that keeps the slide point under `pointer` (or the canvas middle) fixed
|
||||
/// when the scale changed since the last frame.
|
||||
fn anchored_offset(last: Option<(Rect, f32)>, content_rect: Rect, scale: f32, pointer: Option<Pos2>, avail: Rect) -> Option<egui::Vec2> {
|
||||
let (last_rect, last_scale) = last?;
|
||||
if (scale - last_scale).abs() <= 1e-6 {
|
||||
return None;
|
||||
}
|
||||
let anchor = pointer.unwrap_or(avail.center());
|
||||
let at = (anchor - last_rect.min) / last_scale;
|
||||
Some(content_rect.min + at * scale - anchor)
|
||||
}
|
||||
|
||||
fn slide_gifs(app: &mut SlideApp, now: f64) -> Vec<crate::media::Gif> {
|
||||
match app.session.active() {
|
||||
Some(d) if d.selection.target == Target::Slides => app.media.gifs(d.uid, &d.doc, d.selection.slide, now, &app.session.gif_paused),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Paint the editing canvas, including rulers, selection handles, overlays, and current
|
||||
/// zoom/scroll state.
|
||||
pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
@@ -94,17 +157,9 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
// one is drawn here, hovered or not, so switching away and back starts from zero.
|
||||
let carry_id = ui.scope_id().with("wheel_carry");
|
||||
let doc_uid = st.uid;
|
||||
ui.data_mut(|d| {
|
||||
if d.get_temp::<(u64, f32)>(carry_id).is_none_or(|(uid, _)| uid != doc_uid) {
|
||||
d.insert_temp(carry_id, (doc_uid, 0.0_f32));
|
||||
}
|
||||
});
|
||||
reset_wheel_carry(ui, carry_id, doc_uid);
|
||||
if st.doc.slides.is_empty() && st.selection.target == Target::Slides {
|
||||
let resp = ui.allocate_rect(avail, Sense::click());
|
||||
ui.painter().text(avail.center(), Align2::CENTER_CENTER, "Click to add first slide", theme::font(22.0), t.text_dim);
|
||||
if resp.clicked() {
|
||||
let _ = app.run("slide.new", json!({"layout": "title"}));
|
||||
}
|
||||
empty_deck_prompt(app, ui, avail, &t);
|
||||
return;
|
||||
}
|
||||
// Scroll when zoomed beyond the window.
|
||||
@@ -122,12 +177,8 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let (content_rect, scale) = layout(app, avail);
|
||||
// A new zoom keeps the slide point under the pointer (wheel, pinch) or under the middle of the
|
||||
// canvas (Zoom In/Out, the slider) where it was, instead of growing from the corner (#106).
|
||||
if let Some((last_rect, last_scale)) = last
|
||||
&& (scale - last_scale).abs() > 1e-6
|
||||
{
|
||||
let anchor = if zooming { hover.unwrap_or(avail.center()) } else { avail.center() };
|
||||
let at = (anchor - last_rect.min) / last_scale;
|
||||
offset = content_rect.min + at * scale - anchor;
|
||||
if let Some(o) = anchored_offset(last, content_rect, scale, zooming.then_some(hover).flatten(), avail) {
|
||||
offset = o;
|
||||
}
|
||||
let overflow = vec2((content_rect.width() + 56.0 - avail.width()).max(0.0), (content_rect.height() + 56.0 - avail.height()).max(0.0));
|
||||
// Scroll bars on the axes that overflow (#1157). They sit over the canvas edge and take their
|
||||
@@ -175,28 +226,8 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
if (resp.hovered() || on_bar) && !zooming {
|
||||
if overflow != egui::Vec2::ZERO {
|
||||
offset -= wheel;
|
||||
} else if !app.anim_preview_active() && app.session.tool.marquee.is_none() && !app.session.tool.dragging() {
|
||||
let (_, mut carry): (u64, f32) = ui.data_mut(|d| d.get_temp(carry_id).unwrap_or_default());
|
||||
let wheel_step = app.session.active().and_then(|d| {
|
||||
let current = d.selection.slide;
|
||||
ui.input(|i| wheel_slide_target(&i.events, current, d.doc.slides.len(), &mut carry)).map(|target| (current, target))
|
||||
});
|
||||
ui.data_mut(|d| d.insert_temp(carry_id, (doc_uid, carry)));
|
||||
if let Some((current, index)) = wheel_step {
|
||||
if index != current {
|
||||
let _ = app.run("slide.go", json!({"index": index}));
|
||||
}
|
||||
} else if let Some(next) = page_flick(ui.input(|i| trackpad_scroll(&i.events))) {
|
||||
// Point-based trackpad gestures keep their cooldown. Raw input avoids reading
|
||||
// the smoothed tail of a discrete mouse notch as another page turn.
|
||||
let gate = ui.scope_id().with("wheel_gate");
|
||||
let last: f64 = ui.data_mut(|d| d.get_temp(gate).unwrap_or(f64::NEG_INFINITY));
|
||||
let now = ui.input(|i| i.time);
|
||||
if now - last > 0.35 {
|
||||
ui.data_mut(|d| d.insert_temp(gate, now));
|
||||
let _ = app.run(if next { "slide.next" } else { "slide.previous" }, json!({}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wheel_navigation(app, ui, carry_id, doc_uid);
|
||||
}
|
||||
}
|
||||
offset.x = offset.x.clamp(0.0, overflow.x);
|
||||
@@ -218,10 +249,7 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
painter.rect_filled(slide_rect.translate(vec2(0.0, 1.5)).expand(1.0), CornerRadius::same(2), t.shadow);
|
||||
let px = ((slide_rect.width() * ppp).round().max(1.0) as u32, (slide_rect.height() * ppp).round().max(1.0) as u32);
|
||||
let now = ui.input(|i| i.time);
|
||||
let gifs = match app.session.active() {
|
||||
Some(d) if d.selection.target == Target::Slides => app.media.gifs(d.uid, &d.doc, d.selection.slide, now, &app.session.gif_paused),
|
||||
_ => vec![],
|
||||
};
|
||||
let gifs = slide_gifs(app, now);
|
||||
crate::media::gif_repaint(ui.ctx(), &gifs);
|
||||
let times: Vec<_> = gifs.iter().map(|g| (g.id, g.t)).collect();
|
||||
let anim = app.anim_preview_draw();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Hit, SlideApp, json};
|
||||
|
||||
pub(super) fn context_menu(app: &mut SlideApp, resp: &egui::Response) {
|
||||
// Right-clicking a vertex in Edit Points picks it for the point menu.
|
||||
@@ -10,138 +10,12 @@ pub(super) fn context_menu(app: &mut SlideApp, resp: &egui::Response) {
|
||||
resp.context_menu(|ui| {
|
||||
let editing = app.session.active().and_then(|d| app.session.tool.editing_points(d).and_then(|id| d.shape(id).cloned()));
|
||||
if let Some(sh) = editing {
|
||||
if let Some((path, index)) = app.session.tool.edit_segment {
|
||||
let cur = deckcraft_engine::cmd::points::segment_kind_of(&sh, path, index);
|
||||
for (l, k) in [("Straight Segment", "straight"), ("Curved Segment", "curved")] {
|
||||
if ui.radio(cur == Some(k), l).clicked() {
|
||||
let _ = app.run("shape.segmentType", json!({"path": path, "segment": index, "type": k}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
// Each of these renumbers the points, so the picked segment is dropped.
|
||||
if ui.button("Add Point").clicked() {
|
||||
let t = app.session.tool.edit_segment_t.unwrap_or(0.5);
|
||||
let _ = app.run("shape.addPoint", json!({"path": path, "segment": index, "t": t}));
|
||||
app.session.tool.edit_segment = None;
|
||||
ui.close();
|
||||
}
|
||||
let closed = deckcraft_engine::cmd::points::closed_at(&sh, path, index);
|
||||
if ui.add_enabled(closed == Some(true), egui::Button::new("Delete Segment")).clicked() {
|
||||
let _ = app.run("shape.deleteSegment", json!({"path": path, "segment": index}));
|
||||
app.session.tool.edit_segment = None;
|
||||
ui.close();
|
||||
}
|
||||
// Open Path opens the outline at the segment's start point.
|
||||
let (label, id, at) = match closed {
|
||||
Some(false) => ("Close Path", "shape.closePath", index),
|
||||
_ => ("Open Path", "shape.openPath", deckcraft_engine::cmd::points::segment_start_of(&sh, path, index).unwrap_or(index)),
|
||||
};
|
||||
if ui.button(label).clicked() {
|
||||
let _ = app.run(id, json!({"path": path, "index": at}));
|
||||
app.session.tool.edit_segment = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
} else if let Some((path, index)) = app.session.tool.edit_vertex {
|
||||
let cur = deckcraft_engine::cmd::points::kind_of(&sh, path, index);
|
||||
for (l, k) in [("Smooth Point", "smooth"), ("Straight Point", "straight"), ("Corner Point", "corner")] {
|
||||
if ui.radio(cur == Some(k), l).clicked() {
|
||||
let _ = app.run("shape.pointType", json!({"path": path, "index": index, "type": k}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
if ui.button("Delete Point").clicked() {
|
||||
let _ = app.run("shape.deletePoint", json!({"path": path, "index": index}));
|
||||
app.session.tool.edit_vertex = None;
|
||||
ui.close();
|
||||
}
|
||||
let (label, id) = match deckcraft_engine::cmd::points::closed_at(&sh, path, index) {
|
||||
Some(false) => ("Close Path", "shape.closePath"),
|
||||
_ => ("Open Path", "shape.openPath"),
|
||||
};
|
||||
if ui.button(label).clicked() {
|
||||
let _ = app.run(id, json!({"path": path, "index": index}));
|
||||
// The points are numbered from the new start now.
|
||||
app.session.tool.edit_vertex = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
if ui.button("Exit Edit Points").clicked() {
|
||||
let _ = app.run("shape.editPoints", json!({"on": false}));
|
||||
ui.close();
|
||||
}
|
||||
edit_points_menu(app, ui, &sh);
|
||||
return;
|
||||
}
|
||||
// A misspelt word under the pointer: its suggestions, then Ignore All and Add to Dictionary.
|
||||
if let Some(w) = app.squiggles.menu_word.clone() {
|
||||
if let Some(from) = w.delete_from {
|
||||
if ui.button(egui::RichText::new("Delete Repeated Word").strong()).clicked() {
|
||||
let cut: String = format!("{}{}", " ".repeat(w.start - from), w.word);
|
||||
// The gap is spaces; take it from the text so tabs or no-break spaces match.
|
||||
let gap = app
|
||||
.session
|
||||
.active()
|
||||
.and_then(|st| match w.tag {
|
||||
crate::squiggles::Tag::Shape { id, para } => st.shape(id)?.text.as_ref()?.paragraphs.get(para).map(|p| p.text()),
|
||||
crate::squiggles::Tag::Cell { id, row, col, para } => match &st.shape(id)?.kind {
|
||||
deckcraft_model::ShapeKind::Table(tb) => tb.cell(row, col)?.text.paragraphs.get(para).map(|p| p.text()),
|
||||
_ => None,
|
||||
},
|
||||
crate::squiggles::Tag::Notes { .. } => None,
|
||||
})
|
||||
.map(|t| t.chars().skip(from).take(w.start + w.word.chars().count() - from).collect::<String>())
|
||||
.unwrap_or(cut);
|
||||
let mut p = json!({"para": 0, "start": from, "word": gap, "to": ""});
|
||||
if let Some(o) = p.as_object_mut() {
|
||||
match w.tag {
|
||||
crate::squiggles::Tag::Shape { id, para } => {
|
||||
o.insert("shape".into(), json!(id));
|
||||
o.insert("para".into(), json!(para));
|
||||
}
|
||||
crate::squiggles::Tag::Cell { id, row, col, para } => {
|
||||
o.insert("shape".into(), json!(id));
|
||||
o.insert("row".into(), json!(row));
|
||||
o.insert("col".into(), json!(col));
|
||||
o.insert("para".into(), json!(para));
|
||||
}
|
||||
crate::squiggles::Tag::Notes { .. } => {}
|
||||
}
|
||||
}
|
||||
let _ = app.run("review.spellingChange", p);
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
} else if w.suggestions.is_empty() {
|
||||
ui.label(egui::RichText::new("(no spelling suggestions)").weak());
|
||||
}
|
||||
for to in &w.suggestions {
|
||||
if ui.button(egui::RichText::new(to).strong()).clicked() {
|
||||
let p = match w.tag {
|
||||
crate::squiggles::Tag::Shape { id, para } => json!({"shape": id, "para": para, "start": w.start, "word": w.word, "to": to}),
|
||||
crate::squiggles::Tag::Cell { id, row, col, para } => {
|
||||
json!({"shape": id, "row": row, "col": col, "para": para, "start": w.start, "word": w.word, "to": to})
|
||||
}
|
||||
crate::squiggles::Tag::Notes { .. } => json!({}),
|
||||
};
|
||||
let _ = app.run("review.spellingChange", p);
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
if ui.button("Ignore All").clicked() {
|
||||
app.squiggles.ignore(&w.word);
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Add to Dictionary").clicked() {
|
||||
if let Err(e) = app.session.execute("review.addToDictionary", &json!({"word": w.word})) {
|
||||
app.set_status(e.to_string());
|
||||
}
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
spelling_menu(app, ui, &w);
|
||||
}
|
||||
let has_sel = app.session.active().is_some_and(|d| !d.selection.shapes.is_empty());
|
||||
for (l, id) in [("Cut", "edit.cut"), ("Copy", "edit.copy"), ("Paste", "edit.paste")] {
|
||||
@@ -154,129 +28,273 @@ pub(super) fn context_menu(app: &mut SlideApp, resp: &egui::Response) {
|
||||
// Right-clicking the text being edited keeps it (`PointerKind::ContextClick`): text items.
|
||||
let editing = app.session.active().is_some_and(|d| d.selection.text.as_ref().is_some_and(|t| !t.notes));
|
||||
if editing {
|
||||
if ui.button("Paragraph…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("paragraph"));
|
||||
ui.close();
|
||||
}
|
||||
for (l, id) in [("Bullets", "format.bullets"), ("Numbering", "format.numbering")] {
|
||||
if ui.add_enabled(crate::ribbon::enabled(app, id), egui::Button::new(l)).clicked() {
|
||||
let _ = app.run(id, json!({}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
if ui.button("Link…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("hyperlink"));
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Format Shape…").clicked() {
|
||||
let _ = app.run("view.pane", json!({"pane": "format"}));
|
||||
ui.close();
|
||||
}
|
||||
text_items(app, ui);
|
||||
} else if has_sel {
|
||||
if ui.button("Edit Text").clicked() {
|
||||
let _ = app.run("text.edit", json!({}));
|
||||
shape_items(app, ui);
|
||||
} else {
|
||||
slide_items(app, ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn edit_points_menu(app: &mut SlideApp, ui: &mut egui::Ui, sh: &deckcraft_model::Shape) {
|
||||
if let Some((path, index)) = app.session.tool.edit_segment {
|
||||
let cur = deckcraft_engine::cmd::points::segment_kind_of(sh, path, index);
|
||||
for (l, k) in [("Straight Segment", "straight"), ("Curved Segment", "curved")] {
|
||||
if ui.radio(cur == Some(k), l).clicked() {
|
||||
let _ = app.run("shape.segmentType", json!({"path": path, "segment": index, "type": k}));
|
||||
ui.close();
|
||||
}
|
||||
if crate::ribbon::enabled(app, "shape.editPoints")
|
||||
&& app.session.active().is_some_and(|d| d.selection.shapes.len() == 1 && d.selected_shapes().iter().all(|s| !s.is_line()))
|
||||
&& ui.button("Edit Points").clicked()
|
||||
{
|
||||
let _ = app.run("shape.editPoints", json!({"on": true}));
|
||||
}
|
||||
// Each of these renumbers the points, so the picked segment is dropped.
|
||||
if ui.button("Add Point").clicked() {
|
||||
let t = app.session.tool.edit_segment_t.unwrap_or(0.5);
|
||||
let _ = app.run("shape.addPoint", json!({"path": path, "segment": index, "t": t}));
|
||||
app.session.tool.edit_segment = None;
|
||||
ui.close();
|
||||
}
|
||||
let closed = deckcraft_engine::cmd::points::closed_at(sh, path, index);
|
||||
if ui.add_enabled(closed == Some(true), egui::Button::new("Delete Segment")).clicked() {
|
||||
let _ = app.run("shape.deleteSegment", json!({"path": path, "segment": index}));
|
||||
app.session.tool.edit_segment = None;
|
||||
ui.close();
|
||||
}
|
||||
// Open Path opens the outline at the segment's start point.
|
||||
let (label, id, at) = match closed {
|
||||
Some(false) => ("Close Path", "shape.closePath", index),
|
||||
_ => ("Open Path", "shape.openPath", deckcraft_engine::cmd::points::segment_start_of(sh, path, index).unwrap_or(index)),
|
||||
};
|
||||
if ui.button(label).clicked() {
|
||||
let _ = app.run(id, json!({"path": path, "index": at}));
|
||||
app.session.tool.edit_segment = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
} else if let Some((path, index)) = app.session.tool.edit_vertex {
|
||||
let cur = deckcraft_engine::cmd::points::kind_of(sh, path, index);
|
||||
for (l, k) in [("Smooth Point", "smooth"), ("Straight Point", "straight"), ("Corner Point", "corner")] {
|
||||
if ui.radio(cur == Some(k), l).clicked() {
|
||||
let _ = app.run("shape.pointType", json!({"path": path, "index": index, "type": k}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Font…").clicked() {
|
||||
let _ = app.run("format.fontDialog", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Paragraph…").clicked() {
|
||||
let _ = app.run("format.paragraphDialog", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
ui.menu_button("Bring to Front", |ui| {
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::BringToFront, "Bring to Front", "arrange.bringToFront", json!({}));
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::BringForward, "Bring Forward", "arrange.bringForward", json!({}));
|
||||
});
|
||||
ui.menu_button("Send to Back", |ui| {
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::SendToBack, "Send to Back", "arrange.sendToBack", json!({}));
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::SendBackward, "Send Backward", "arrange.sendBackward", json!({}));
|
||||
});
|
||||
ui.menu_button("Group", |ui| {
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::Group, "Group", "arrange.group", json!({}));
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::Ungroup, "Ungroup", "arrange.ungroup", json!({}));
|
||||
});
|
||||
let locked = app.session.active().is_some_and(|d| d.selected_shapes().iter().all(|s| s.locked));
|
||||
if ui.button(if locked { "Unlock" } else { "Lock" }).clicked() {
|
||||
let _ = app.run("shape.lock", json!({"locked": !locked}));
|
||||
ui.close();
|
||||
}
|
||||
let picture = app
|
||||
}
|
||||
if ui.button("Delete Point").clicked() {
|
||||
let _ = app.run("shape.deletePoint", json!({"path": path, "index": index}));
|
||||
app.session.tool.edit_vertex = None;
|
||||
ui.close();
|
||||
}
|
||||
let (label, id) = match deckcraft_engine::cmd::points::closed_at(sh, path, index) {
|
||||
Some(false) => ("Close Path", "shape.closePath"),
|
||||
_ => ("Open Path", "shape.openPath"),
|
||||
};
|
||||
if ui.button(label).clicked() {
|
||||
let _ = app.run(id, json!({"path": path, "index": index}));
|
||||
// The points are numbered from the new start now.
|
||||
app.session.tool.edit_vertex = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
if ui.button("Exit Edit Points").clicked() {
|
||||
let _ = app.run("shape.editPoints", json!({"on": false}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
fn spelling_menu(app: &mut SlideApp, ui: &mut egui::Ui, w: &crate::squiggles::MenuWord) {
|
||||
if let Some(from) = w.delete_from {
|
||||
if ui.button(egui::RichText::new("Delete Repeated Word").strong()).clicked() {
|
||||
let cut: String = format!("{}{}", " ".repeat(w.start - from), w.word);
|
||||
// The gap is spaces; take it from the text so tabs or no-break spaces match.
|
||||
let gap = app
|
||||
.session
|
||||
.active()
|
||||
.is_some_and(|d| d.selected_shapes().iter().any(|s| matches!(s.kind, deckcraft_model::ShapeKind::Picture { .. })));
|
||||
if picture && ui.button("Change Picture…").clicked() {
|
||||
let _ = app.run("app.changePictureDialog", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Link…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("hyperlink"));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Edit Alt Text…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("altText"));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Set as Default Shape").clicked() {
|
||||
let _ = app.run("shape.setDefault", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Size and Position…").clicked() {
|
||||
let _ = app.run("shape.sizePosition", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Format Shape…").clicked() {
|
||||
let _ = app.run("view.pane", json!({"pane": "format"}));
|
||||
ui.close();
|
||||
}
|
||||
} else {
|
||||
ui.menu_button("Layout", |ui| {
|
||||
let layouts: Vec<(String, u32)> = app
|
||||
.session
|
||||
.active()
|
||||
.and_then(|d| d.doc.masters.first().map(|m| m.layouts.iter().map(|l| (l.name.clone(), l.id.0)).collect()))
|
||||
.unwrap_or_default();
|
||||
for (n, id) in layouts {
|
||||
if ui.button(n).clicked() {
|
||||
let _ = app.run("slide.layout", json!({"layout": id}));
|
||||
ui.close();
|
||||
.and_then(|st| match w.tag {
|
||||
crate::squiggles::Tag::Shape { id, para } => st.shape(id)?.text.as_ref()?.paragraphs.get(para).map(|p| p.text()),
|
||||
crate::squiggles::Tag::Cell { id, row, col, para } => match &st.shape(id)?.kind {
|
||||
deckcraft_model::ShapeKind::Table(tb) => tb.cell(row, col)?.text.paragraphs.get(para).map(|p| p.text()),
|
||||
_ => None,
|
||||
},
|
||||
crate::squiggles::Tag::Notes { .. } => None,
|
||||
})
|
||||
.map(|t| t.chars().skip(from).take(w.start + w.word.chars().count() - from).collect::<String>())
|
||||
.unwrap_or(cut);
|
||||
let mut p = json!({"para": 0, "start": from, "word": gap, "to": ""});
|
||||
if let Some(o) = p.as_object_mut() {
|
||||
match w.tag {
|
||||
crate::squiggles::Tag::Shape { id, para } => {
|
||||
o.insert("shape".into(), json!(id));
|
||||
o.insert("para".into(), json!(para));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.menu_button("Grid and Guides", |ui| {
|
||||
let mut on = app.ui.guides;
|
||||
if ui.checkbox(&mut on, "Guides").clicked() {
|
||||
let _ = app.run("view.guides", json!({"on": on}));
|
||||
}
|
||||
for (label, vertical) in [("Add Vertical Guide", true), ("Add Horizontal Guide", false)] {
|
||||
if ui.button(label).clicked() {
|
||||
let _ = app.run("guide.add", json!({"vertical": vertical}));
|
||||
let _ = app.run("view.guides", json!({"on": true}));
|
||||
ui.close();
|
||||
crate::squiggles::Tag::Cell { id, row, col, para } => {
|
||||
o.insert("shape".into(), json!(id));
|
||||
o.insert("row".into(), json!(row));
|
||||
o.insert("col".into(), json!(col));
|
||||
o.insert("para".into(), json!(para));
|
||||
}
|
||||
crate::squiggles::Tag::Notes { .. } => {}
|
||||
}
|
||||
});
|
||||
if ui.button("Reset Slide").clicked() {
|
||||
let _ = app.run("slide.reset", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Format Background…").clicked() {
|
||||
let _ = app.run("view.pane", json!({"pane": "background"}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("New Comment").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("comment"));
|
||||
let _ = app.run("review.spellingChange", p);
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
} else if w.suggestions.is_empty() {
|
||||
ui.label(egui::RichText::new("(no spelling suggestions)").weak());
|
||||
}
|
||||
for to in &w.suggestions {
|
||||
if ui.button(egui::RichText::new(to).strong()).clicked() {
|
||||
let p = match w.tag {
|
||||
crate::squiggles::Tag::Shape { id, para } => json!({"shape": id, "para": para, "start": w.start, "word": w.word, "to": to}),
|
||||
crate::squiggles::Tag::Cell { id, row, col, para } => {
|
||||
json!({"shape": id, "row": row, "col": col, "para": para, "start": w.start, "word": w.word, "to": to})
|
||||
}
|
||||
crate::squiggles::Tag::Notes { .. } => json!({}),
|
||||
};
|
||||
let _ = app.run("review.spellingChange", p);
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
if ui.button("Ignore All").clicked() {
|
||||
app.squiggles.ignore(&w.word);
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Add to Dictionary").clicked() {
|
||||
if let Err(e) = app.session.execute("review.addToDictionary", &json!({"word": w.word})) {
|
||||
app.set_status(e.to_string());
|
||||
}
|
||||
app.squiggles.menu_word = None;
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
fn text_items(app: &mut SlideApp, ui: &mut egui::Ui) {
|
||||
if ui.button("Paragraph…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("paragraph"));
|
||||
ui.close();
|
||||
}
|
||||
for (l, id) in [("Bullets", "format.bullets"), ("Numbering", "format.numbering")] {
|
||||
if ui.add_enabled(crate::ribbon::enabled(app, id), egui::Button::new(l)).clicked() {
|
||||
let _ = app.run(id, json!({}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
if ui.button("Link…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("hyperlink"));
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Format Shape…").clicked() {
|
||||
let _ = app.run("view.pane", json!({"pane": "format"}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
fn shape_items(app: &mut SlideApp, ui: &mut egui::Ui) {
|
||||
if ui.button("Edit Text").clicked() {
|
||||
let _ = app.run("text.edit", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if crate::ribbon::enabled(app, "shape.editPoints")
|
||||
&& app.session.active().is_some_and(|d| d.selection.shapes.len() == 1 && d.selected_shapes().iter().all(|s| !s.is_line()))
|
||||
&& ui.button("Edit Points").clicked()
|
||||
{
|
||||
let _ = app.run("shape.editPoints", json!({"on": true}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Font…").clicked() {
|
||||
let _ = app.run("format.fontDialog", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Paragraph…").clicked() {
|
||||
let _ = app.run("format.paragraphDialog", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
ui.menu_button("Bring to Front", |ui| {
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::BringToFront, "Bring to Front", "arrange.bringToFront", json!({}));
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::BringForward, "Bring Forward", "arrange.bringForward", json!({}));
|
||||
});
|
||||
ui.menu_button("Send to Back", |ui| {
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::SendToBack, "Send to Back", "arrange.sendToBack", json!({}));
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::SendBackward, "Send Backward", "arrange.sendBackward", json!({}));
|
||||
});
|
||||
ui.menu_button("Group", |ui| {
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::Group, "Group", "arrange.group", json!({}));
|
||||
crate::ribbon::menu_item(app, ui, crate::icons::Icon::Ungroup, "Ungroup", "arrange.ungroup", json!({}));
|
||||
});
|
||||
let locked = app.session.active().is_some_and(|d| d.selected_shapes().iter().all(|s| s.locked));
|
||||
if ui.button(if locked { "Unlock" } else { "Lock" }).clicked() {
|
||||
let _ = app.run("shape.lock", json!({"locked": !locked}));
|
||||
ui.close();
|
||||
}
|
||||
let picture =
|
||||
app.session.active().is_some_and(|d| d.selected_shapes().iter().any(|s| matches!(s.kind, deckcraft_model::ShapeKind::Picture { .. })));
|
||||
if picture && ui.button("Change Picture…").clicked() {
|
||||
let _ = app.run("app.changePictureDialog", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Link…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("hyperlink"));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Edit Alt Text…").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("altText"));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Set as Default Shape").clicked() {
|
||||
let _ = app.run("shape.setDefault", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Size and Position…").clicked() {
|
||||
let _ = app.run("shape.sizePosition", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Format Shape…").clicked() {
|
||||
let _ = app.run("view.pane", json!({"pane": "format"}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
fn slide_items(app: &mut SlideApp, ui: &mut egui::Ui) {
|
||||
ui.menu_button("Layout", |ui| {
|
||||
let layouts: Vec<(String, u32)> = app
|
||||
.session
|
||||
.active()
|
||||
.and_then(|d| d.doc.masters.first().map(|m| m.layouts.iter().map(|l| (l.name.clone(), l.id.0)).collect()))
|
||||
.unwrap_or_default();
|
||||
for (n, id) in layouts {
|
||||
if ui.button(n).clicked() {
|
||||
let _ = app.run("slide.layout", json!({"layout": id}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.menu_button("Grid and Guides", |ui| {
|
||||
let mut on = app.ui.guides;
|
||||
if ui.checkbox(&mut on, "Guides").clicked() {
|
||||
let _ = app.run("view.guides", json!({"on": on}));
|
||||
}
|
||||
for (label, vertical) in [("Add Vertical Guide", true), ("Add Horizontal Guide", false)] {
|
||||
if ui.button(label).clicked() {
|
||||
let _ = app.run("guide.add", json!({"vertical": vertical}));
|
||||
let _ = app.run("view.guides", json!({"on": true}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
if ui.button("Reset Slide").clicked() {
|
||||
let _ = app.run("slide.reset", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Format Background…").clicked() {
|
||||
let _ = app.run("view.pane", json!({"pane": "background"}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("New Comment").clicked() {
|
||||
app.dialog = Some(crate::dialogs::Dialog::new("comment"));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Color32, CornerRadius, Point, Rect, SlideApp, Stroke, Tokens, Xf, pos2, theme};
|
||||
|
||||
/// The presentation's drawing guides; the one being dragged at its new place, labelled with its
|
||||
/// distance from the slide's centre in points.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Rect, SlideApp, Ui, Xf, screen_rect, vec2};
|
||||
|
||||
/// Where an input method composes for the slide (#101), on screen: at the insertion point while
|
||||
/// editing text, or over the one selected shape (typing on it starts editing). `None` when there is
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Hit, Point, PointerEvent, PointerKind, SlideApp, ToolKind, Ui, Xf, json, mods};
|
||||
|
||||
/// A two-finger twist on the trackpad. macOS delivers it as [`egui::Event::Rotate`];
|
||||
/// other platforms leave the delta at zero.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Rect, Sense, SlideApp, Target, Ui, Xf, json, pos2, screen_rect, vec2};
|
||||
|
||||
/// Play/pause on an animated GIF under the pointer or selected on its own.
|
||||
pub(super) fn gif_buttons(app: &mut SlideApp, ui: &mut Ui, xf: Xf, gifs: &[crate::media::Gif]) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use super::{Color32, CornerRadius, Point, Pos2, Rect, SlideApp, Stroke, Tokens, ToolKind, Ui, Xf, Xfrm, box_handles, pos2, theme};
|
||||
use deckcraft_engine::DocState;
|
||||
|
||||
fn poly(p: &egui::Painter, pts: [Pos2; 4], stroke: Stroke) {
|
||||
for i in 0..4 {
|
||||
@@ -6,16 +7,7 @@ fn poly(p: &egui::Painter, pts: [Pos2; 4], stroke: Stroke) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn overlays(app: &mut SlideApp, ui: &Ui, painter: &egui::Painter, xf: Xf, t: &Tokens) {
|
||||
// Spelling squiggles under misspelt words (checked in the background).
|
||||
if let Some(st) = app.session.active() {
|
||||
let lines = app.squiggles.update(st, Some(ui.ctx()));
|
||||
let scale = (xf.to_screen(Point::new(1.0, 0.0)) - xf.to_screen(Point::new(0.0, 0.0))).length();
|
||||
for (a, b) in lines {
|
||||
crate::squiggles::paint(painter, xf.to_screen(a), xf.to_screen(b), scale);
|
||||
}
|
||||
}
|
||||
let Some(st) = app.session.active() else { return };
|
||||
fn selection_handles(app: &SlideApp, st: &DocState, painter: &egui::Painter, xf: Xf, t: &Tokens) {
|
||||
let sel_stroke = Stroke::new(1.0, t.selection);
|
||||
let editing = st.selection.text.clone();
|
||||
for id in &st.selection.shapes {
|
||||
@@ -104,6 +96,10 @@ pub(super) fn overlays(app: &mut SlideApp, ui: &Ui, painter: &egui::Painter, xf:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn text_overlays(st: &DocState, ui: &Ui, painter: &egui::Painter, xf: Xf, t: &Tokens) {
|
||||
let editing = st.selection.text.clone();
|
||||
// Caret / text selection, in a shape or a table cell.
|
||||
if let Some(ts) = &editing {
|
||||
for quad in text_marks(st, ts).iter().flat_map(|m| &m.selection) {
|
||||
@@ -148,6 +144,20 @@ pub(super) fn overlays(app: &mut SlideApp, ui: &Ui, painter: &egui::Painter, xf:
|
||||
painter.add(egui::Shape::convex_polygon(pts.iter().map(|q| xf.to_screen(f.affine * *q)).collect(), t.text_selection, Stroke::NONE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn overlays(app: &mut SlideApp, ui: &Ui, painter: &egui::Painter, xf: Xf, t: &Tokens) {
|
||||
// Spelling squiggles under misspelt words (checked in the background).
|
||||
if let Some(st) = app.session.active() {
|
||||
let lines = app.squiggles.update(st, Some(ui.ctx()));
|
||||
let scale = (xf.to_screen(Point::new(1.0, 0.0)) - xf.to_screen(Point::new(0.0, 0.0))).length();
|
||||
for (a, b) in lines {
|
||||
crate::squiggles::paint(painter, xf.to_screen(a), xf.to_screen(b), scale);
|
||||
}
|
||||
}
|
||||
let Some(st) = app.session.active() else { return };
|
||||
selection_handles(app, st, painter, xf, t);
|
||||
text_overlays(st, ui, painter, xf, t);
|
||||
// Smart guides, marquee, previews.
|
||||
for g in &app.session.tool.guides {
|
||||
let (a, b) =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Align2, Color32, CornerRadius, Point, Pos2, Rect, Sense, SlideApp, Stroke, Tokens, Ui, Xf, json, pos2, theme, vec2};
|
||||
|
||||
/// The ruler's numbered step in points and the ticks per step: quarter inches, or half
|
||||
/// centimetres. Any other unit (only a hand-edited `prefs.json` gives one) keeps inches, since a
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Rect, pos2};
|
||||
|
||||
/// Thickness of the canvas scroll bars, in points.
|
||||
const SCROLL_BAR: f32 = 10.0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Color32, CornerRadius, Rect, Sense, SlideApp, Stroke, Tokens, Ui, pos2, theme, vec2};
|
||||
|
||||
/// Room the About credits need for their table; the About tab keeps its compact size.
|
||||
pub(super) const CREDITS_SIZE: egui::Vec2 = vec2(640.0, 380.0);
|
||||
@@ -70,7 +70,8 @@ mod about_credits_tests {
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
||||
use super::{CREDITS_SIZE, Dialog, credits_block};
|
||||
use super::{CREDITS_SIZE, credits_block};
|
||||
use crate::dialogs::Dialog;
|
||||
use crate::{Services, SlideApp};
|
||||
|
||||
fn tab_of(h: &Harness<'_, SlideApp>) -> String {
|
||||
|
||||
+1352
-1302
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Color32, Dialog, SlideApp, Ui, Value, buttons, field, field_of, json, num_text};
|
||||
|
||||
/// The Columns dialog's changed fields as `format.columns` params: Number and Spacing (inches,
|
||||
/// sent in points). A field left as the dialog showed it is left out, so each selected text box
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{DATE_KINDS, Dialog, SlideApp, Tokens, Ui, buttons, check, json};
|
||||
|
||||
const DATE_LANGS: &[(&str, &str)] = &[
|
||||
("en-US", "English (United States)"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, Value, check, field, json, num, vec2};
|
||||
|
||||
/// The export dialog's Animated GIF options (size, seconds per slide, loop, slide range, hidden
|
||||
/// slides) as `file.export` params; a slide range that doesn't fit the deck is an error message.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, Value, check, field, json, vec2};
|
||||
|
||||
/// Home › Find / Replace: step through matches (slide text, table cells, notes) or replace them.
|
||||
pub(super) fn find_dialog(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, buttons, check, choice, color_label, finite_pt, json, json_text, labeled_text, seed_once, vec2};
|
||||
|
||||
fn seed_font_fields(d: &mut Dialog) {
|
||||
seed_once(d, "family", json_text(d.params.get("family"), ""));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Color32, Dialog, SlideApp, Ui, buttons, check, field, json, num, theme};
|
||||
|
||||
fn format_spacing(v: f64) -> String {
|
||||
if !v.is_finite() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Value, json};
|
||||
|
||||
/// What Insert Hyperlink would edit on the current selection.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::SlideApp;
|
||||
|
||||
/// The selection's proofing language and whether it's marked Do Not Check Spelling: the run at
|
||||
/// the caret, else the first run of the first selected shape; US English when none says.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, Value, buttons, choice, finite_pt, inches_text, json, json_text, labeled_text, pt_text, seed_once, vec2};
|
||||
|
||||
fn seed_paragraph_fields(d: &mut Dialog) {
|
||||
seed_once(d, "align", json_text(d.params.get("align"), "left"));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Tokens, Ui, Value, buttons, check, field, json};
|
||||
|
||||
fn album_items(d: &Dialog) -> Option<Vec<Value>> {
|
||||
let raw = d.fields.get("items").map(String::as_str).unwrap_or("[]");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Value, json, theme};
|
||||
|
||||
/// What the Preferences dialog can change, as it was when the dialog opened.
|
||||
pub(super) fn preferences_snapshot(app: &SlideApp) -> Value {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, Value, buttons, check, edited, exact_of, field_of, json, num_text, parse_shown, text_row};
|
||||
|
||||
fn put_dim(d: &mut Dialog, key: &str, exact: f64, shown: String) {
|
||||
d.fields.insert(key.into(), shown.clone());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, exact_of, field_of};
|
||||
|
||||
/// A Slide Size field set to `pt`: two decimals of the unit shown, the exact points kept beside.
|
||||
pub(super) fn put_slide_dim(d: &mut Dialog, key: &str, pt: f64, ppu: f64) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, Value, buttons, field, json, num, vec2};
|
||||
|
||||
const TAB_KINDS: [(&str, &str); 4] = [("l", "Left"), ("ctr", "Center"), ("r", "Right"), ("dec", "Decimal")];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Color32, CornerRadius, Dialog, SchemeSlot, Sense, SlideApp, Stroke, Tokens, Ui, Value, buttons, json, theme, vec2};
|
||||
|
||||
fn seed_theme_colors(d: &mut Dialog) {
|
||||
if d.fields.contains_key("seeded") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Tokens, Ui, Value, buttons, json, vec2};
|
||||
|
||||
fn seed_theme_fonts(d: &mut Dialog) {
|
||||
if d.fields.contains_key("seeded") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Dialog, SlideApp, Ui, buttons};
|
||||
|
||||
/// Preset percents in the Zoom dialog, largest first.
|
||||
const ZOOM_PRESETS: &[u32] = &[400, 200, 150, 100, 75, 66, 50, 33];
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::SlideApp;
|
||||
pub struct DocWindow {
|
||||
pub uid: u64,
|
||||
pub slide: usize,
|
||||
key: Option<(u64, u64, usize, u32, u32, bool)>,
|
||||
key: Option<TexKey>,
|
||||
tex: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ pub fn show(app: &mut SlideApp, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.canvas).inner_margin(egui::Margin::same(16))).show(ui, |ui| {
|
||||
let avail = ui.available_rect_before_wrap();
|
||||
let (w, h) = pixel_size(&doc, avail.width());
|
||||
let tex = ensure_texture(app, ui.ctx(), &doc, uid, revision, slide, w, h, grayscale);
|
||||
let tex = ensure_texture(app, ui.ctx(), &doc, (uid, revision, slide, w, h, grayscale));
|
||||
if let Some(tex) = tex {
|
||||
let fitted = fit_rect(avail, w, h);
|
||||
ui.painter().rect_filled(fitted, CornerRadius::ZERO, Color32::WHITE);
|
||||
@@ -141,18 +141,11 @@ fn fit_rect(avail: Rect, w: u32, h: u32) -> Rect {
|
||||
Rect::from_center_size(avail.center(), vec2(width.max(1.0), height.max(1.0)))
|
||||
}
|
||||
|
||||
fn ensure_texture(
|
||||
app: &mut SlideApp,
|
||||
ctx: &egui::Context,
|
||||
doc: &deckcraft_model::Presentation,
|
||||
uid: u64,
|
||||
revision: u64,
|
||||
slide: usize,
|
||||
w: u32,
|
||||
h: u32,
|
||||
grayscale: bool,
|
||||
) -> Option<egui::TextureHandle> {
|
||||
let key = (uid, revision, slide, w, h, grayscale);
|
||||
/// What a cached slide texture was drawn from: document, revision, slide, pixel size, grayscale.
|
||||
type TexKey = (u64, u64, usize, u32, u32, bool);
|
||||
|
||||
fn ensure_texture(app: &mut SlideApp, ctx: &egui::Context, doc: &deckcraft_model::Presentation, key: TexKey) -> Option<egui::TextureHandle> {
|
||||
let (_, _, slide, w, h, grayscale) = key;
|
||||
if let Some(win) = app.doc_window.as_ref()
|
||||
&& win.key == Some(key)
|
||||
&& let Some(tex) = win.tex.clone()
|
||||
|
||||
+218
-177
@@ -1,19 +1,26 @@
|
||||
use super::geometry::{B, C, D, G, Glyph, K, N, O, P2, PU, Pen, R, W, Y, arc_pts, cubic_pts, quad_pts, rad, round_pts, rrect_pts, t, tf};
|
||||
use super::*;
|
||||
use super::{Icon, PI};
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// The icons
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
let _ = draw_clipboard_to_drawing(p, icon)
|
||||
|| draw_insert_to_design(p, icon)
|
||||
|| draw_motion_to_view(p, icon)
|
||||
|| draw_arrange_to_media(p, icon)
|
||||
|| draw_chrome_and_status(p, icon);
|
||||
}
|
||||
|
||||
fn draw_clipboard_to_drawing(p: &Pen<'_>, icon: Icon) -> bool {
|
||||
use Icon as I;
|
||||
match icon {
|
||||
// ----- Clipboard & slides -----
|
||||
I::Paste => {
|
||||
p.rline(&[(10.5, 21.5), (3.5, 21.5), (3.5, 4.5), (17.0, 4.5), (17.0, 9.0)], 2.0, K);
|
||||
p.rect(7.0, 2.5, 13.5, 6.5, 1.2, Y, K);
|
||||
p.rect(10.5, 10.0, 21.0, 22.0, 1.2, t(B), B);
|
||||
p.rect((7.0, 2.5, 13.5, 6.5), 1.2, Y, K);
|
||||
p.rect((10.5, 10.0, 21.0, 22.0), 1.2, t(B), B);
|
||||
p.seg((13.0, 14.0), (18.5, 14.0), B);
|
||||
p.seg((13.0, 18.0), (17.0, 18.0), B);
|
||||
}
|
||||
@@ -26,14 +33,14 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Copy => {
|
||||
p.rline(&[(8.0, 16.0), (3.0, 16.0), (3.0, 2.5), (14.0, 2.5), (14.0, 7.0)], 1.5, K);
|
||||
p.rect(8.0, 7.5, 20.5, 21.5, 1.5, t(B), B);
|
||||
p.rect((8.0, 7.5, 20.5, 21.5), 1.5, t(B), B);
|
||||
p.seg((11.0, 12.5), (17.5, 12.5), B);
|
||||
p.seg((11.0, 16.5), (16.0, 16.5), B);
|
||||
}
|
||||
I::FormatPainter => {
|
||||
p.rect(2.5, 3.0, 17.0, 9.0, 1.5, Y, K);
|
||||
p.rect((2.5, 3.0, 17.0, 9.0), 1.5, Y, K);
|
||||
p.rline(&[(17.0, 6.0), (20.5, 6.0), (20.5, 11.5), (11.0, 11.5), (11.0, 14.0)], 1.2, K);
|
||||
p.rect(9.0, 14.0, 13.0, 21.5, 1.0, K, K);
|
||||
p.rect((9.0, 14.0, 13.0, 21.5), 1.0, K, K);
|
||||
}
|
||||
I::NewSlide => {
|
||||
p.frame(2.0, 4.0, 19.5, 16.5);
|
||||
@@ -43,9 +50,9 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Layout => {
|
||||
p.frame(2.0, 3.5, 22.0, 20.5);
|
||||
p.rect(5.0, 6.5, 19.0, 9.0, 0.8, B, N);
|
||||
p.rect(5.0, 11.5, 11.0, 17.5, 0.8, t(G), G);
|
||||
p.rect(13.0, 11.5, 19.0, 17.5, 0.8, t(O), O);
|
||||
p.rect((5.0, 6.5, 19.0, 9.0), 0.8, B, N);
|
||||
p.rect((5.0, 11.5, 11.0, 17.5), 0.8, t(G), G);
|
||||
p.rect((13.0, 11.5, 19.0, 17.5), 0.8, t(O), O);
|
||||
}
|
||||
I::ResetSlide => {
|
||||
p.frame(2.0, 3.5, 22.0, 20.5);
|
||||
@@ -54,12 +61,12 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
I::Section => {
|
||||
p.rshape(&[(2.5, 2.5), (13.0, 2.5), (16.5, 5.75), (13.0, 9.0), (2.5, 9.0)], 0.6, PU, N);
|
||||
p.rline(&[(5.0, 9.0), (5.0, 16.0), (7.5, 16.0)], 1.0, K);
|
||||
p.rect(7.5, 12.0, 21.5, 20.5, 1.2, N, K);
|
||||
p.rect((7.5, 12.0, 21.5, 20.5), 1.2, N, K);
|
||||
p.seg((10.0, 15.5), (16.0, 15.5), K.a(0.5));
|
||||
}
|
||||
I::DuplicateSlide => {
|
||||
p.rline(&[(6.5, 15.5), (2.5, 15.5), (2.5, 3.5), (16.5, 3.5), (16.5, 8.0)], 1.5, K);
|
||||
p.rect(6.5, 8.0, 21.5, 20.5, 1.5, t(B), B);
|
||||
p.rect((6.5, 8.0, 21.5, 20.5), 1.5, t(B), B);
|
||||
p.seg((14.0, 11.5), (14.0, 17.0), B);
|
||||
p.seg((11.25, 14.25), (16.75, 14.25), B);
|
||||
}
|
||||
@@ -70,14 +77,14 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.badge((18.0, 17.5), 5.0, R, Glyph::Cross);
|
||||
}
|
||||
I::HideSlide => {
|
||||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, N, K.a(0.55));
|
||||
p.rect((2.0, 4.0, 22.0, 20.0), 1.5, N, K.a(0.55));
|
||||
p.seg((5.0, 8.0), (13.0, 8.0), K.a(0.55));
|
||||
p.seg((3.0, 21.0), (21.0, 3.0), R);
|
||||
}
|
||||
I::Slide => {
|
||||
p.frame(2.0, 4.5, 22.0, 19.5);
|
||||
p.seg((5.0, 8.5), (15.0, 8.5), B);
|
||||
p.rect(5.0, 11.5, 19.0, 16.5, 0.8, t(B), N);
|
||||
p.rect((5.0, 11.5, 19.0, 16.5), 0.8, t(B), N);
|
||||
}
|
||||
|
||||
// ----- Font -----
|
||||
@@ -136,11 +143,11 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
let m = |pts: &[P2]| tf((8.0, 15.0), -45.0, pts);
|
||||
p.poly(&m(&[(0.0, -1.2), (3.0, -3.0), (3.0, 3.0), (0.0, 1.4)]), Y, K);
|
||||
p.poly(&m(&[(3.0, -3.0), (13.5, -3.0), (13.5, 3.0), (3.0, 3.0)]), N, K);
|
||||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, Y, N);
|
||||
p.rect((2.5, 19.5, 21.5, 22.5), 0.8, Y, N);
|
||||
}
|
||||
I::FontColor => {
|
||||
p.letter_a(4.5, 2.5, 19.5, 17.0, K, 1.1);
|
||||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, R, N);
|
||||
p.rect((2.5, 19.5, 21.5, 22.5), 0.8, R, N);
|
||||
}
|
||||
I::TextShadow => {
|
||||
p.text("S", (14.0, 13.8), 22.0, K.a(0.3));
|
||||
@@ -201,7 +208,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.arrow((21.0, 3.5), (21.0, 20.5), B);
|
||||
}
|
||||
I::AlignText => {
|
||||
p.rect(2.5, 3.0, 15.0, 21.0, 1.2, N, K);
|
||||
p.rect((2.5, 3.0, 15.0, 21.0), 1.2, N, K);
|
||||
p.seg((5.5, 10.0), (12.0, 10.0), K);
|
||||
p.seg((5.5, 14.0), (10.5, 14.0), K);
|
||||
p.arrow((19.5, 2.5), (19.5, 9.5), B);
|
||||
@@ -212,27 +219,27 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg((2.5, y), (x1, y), K);
|
||||
}
|
||||
p.path_arrow(&[(14.0, 6.0), (19.0, 6.0), (19.0, 12.5)], B);
|
||||
p.rect(2.5, 15.5, 7.5, 21.5, 1.0, B, N);
|
||||
p.rect(9.5, 15.5, 14.5, 21.5, 1.0, O, N);
|
||||
p.rect(16.5, 15.5, 21.5, 21.5, 1.0, G, N);
|
||||
p.rect((2.5, 15.5, 7.5, 21.5), 1.0, B, N);
|
||||
p.rect((9.5, 15.5, 14.5, 21.5), 1.0, O, N);
|
||||
p.rect((16.5, 15.5, 21.5, 21.5), 1.0, G, N);
|
||||
}
|
||||
|
||||
// ----- Drawing -----
|
||||
I::Shapes => {
|
||||
p.rect(11.0, 2.5, 21.5, 13.0, 1.0, t(G), G);
|
||||
p.rect((11.0, 2.5, 21.5, 13.0), 1.0, t(G), G);
|
||||
p.rshape(&[(15.25, 11.0), (21.5, 21.5), (9.0, 21.5)], 0.8, t(O), O);
|
||||
p.circle((8.0, 9.0), 5.5, t(B), B);
|
||||
}
|
||||
I::Arrange => {
|
||||
p.rect(2.5, 2.5, 11.5, 11.5, 1.0, N, K);
|
||||
p.rect(7.5, 7.5, 16.5, 16.5, 1.0, t(B), B);
|
||||
p.rect(12.5, 12.5, 21.5, 21.5, 1.0, B, B);
|
||||
p.rect((2.5, 2.5, 11.5, 11.5), 1.0, N, K);
|
||||
p.rect((7.5, 7.5, 16.5, 16.5), 1.0, t(B), B);
|
||||
p.rect((12.5, 12.5, 21.5, 21.5), 1.0, B, B);
|
||||
}
|
||||
I::QuickStyles => {
|
||||
p.rect(3.0, 3.0, 11.0, 11.0, 1.5, B, N);
|
||||
p.rect(13.0, 3.0, 21.0, 11.0, 1.5, N, O);
|
||||
p.rect(3.0, 13.0, 11.0, 21.0, 1.5, t(G), G);
|
||||
p.rect(13.0, 13.0, 21.0, 21.0, 1.5, PU, N);
|
||||
p.rect((3.0, 3.0, 11.0, 11.0), 1.5, B, N);
|
||||
p.rect((13.0, 3.0, 21.0, 11.0), 1.5, N, O);
|
||||
p.rect((3.0, 13.0, 11.0, 21.0), 1.5, t(G), G);
|
||||
p.rect((13.0, 13.0, 21.0, 21.0), 1.5, PU, N);
|
||||
}
|
||||
I::ShapeFill => {
|
||||
let m = |pts: &[P2]| tf((10.0, 10.0), 45.0, pts);
|
||||
@@ -241,26 +248,26 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.line(&quad_pts((5.2, 6.4), (5.5, 1.0), (10.0, 3.0), 10), K);
|
||||
p.poly(&[(18.5, 8.5), (20.6, 12.6), (16.4, 12.6)], B, N);
|
||||
p.dot((18.5, 13.4), 2.1, B);
|
||||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, B, N);
|
||||
p.rect((2.5, 19.5, 21.5, 22.5), 0.8, B, N);
|
||||
}
|
||||
I::ShapeOutline => {
|
||||
p.tool((4.5, 15.5), -45.0, 17.0, 2.4, 4.2, N, K, 0.0, N);
|
||||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, O, N);
|
||||
p.tool(((4.5, 15.5), -45.0), (17.0, 2.4, 4.2), N, K, (0.0, N));
|
||||
p.rect((2.5, 19.5, 21.5, 22.5), 0.8, O, N);
|
||||
}
|
||||
I::ShapeEffects => {
|
||||
p.rect(6.0, 8.0, 18.5, 20.5, 2.0, K.a(0.25), N);
|
||||
p.rect(3.0, 5.0, 15.5, 17.5, 2.0, B, N);
|
||||
p.rect((6.0, 8.0, 18.5, 20.5), 2.0, K.a(0.25), N);
|
||||
p.rect((3.0, 5.0, 15.5, 17.5), 2.0, B, N);
|
||||
p.sparkle((19.0, 5.0), 4.0, Y);
|
||||
}
|
||||
I::TextFill => {
|
||||
p.letter_a(4.5, 2.5, 19.5, 17.0, B, 1.5);
|
||||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, B, N);
|
||||
p.rect((2.5, 19.5, 21.5, 22.5), 0.8, B, N);
|
||||
}
|
||||
I::TextOutline => {
|
||||
let outer = [(3.5, 17.0), (9.6, 2.5), (14.4, 2.5), (20.5, 17.0), (16.6, 17.0), (15.2, 13.2), (8.8, 13.2), (7.4, 17.0)];
|
||||
p.shape_w(&outer, N, K, 0.8);
|
||||
p.poly_w(&[(10.0, 10.0), (12.0, 5.0), (14.0, 10.0)], N, K, 0.8);
|
||||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, O, N);
|
||||
p.rect((2.5, 19.5, 21.5, 22.5), 0.8, O, N);
|
||||
}
|
||||
I::TextEffects => {
|
||||
p.letter_a(3.0, 5.0, 17.0, 21.0, PU, 1.3);
|
||||
@@ -275,7 +282,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
pts.extend(cubic_pts(c, (15.0, 22.0), (9.0, 17.0), a, 16));
|
||||
p.shape(&pts, t(B), K);
|
||||
for q in [a, b, c] {
|
||||
p.rect(q.0 - 1.6, q.1 - 1.6, q.0 + 1.6, q.1 + 1.6, 0.3, B, N);
|
||||
p.rect((q.0 - 1.6, q.1 - 1.6, q.0 + 1.6, q.1 + 1.6), 0.3, B, N);
|
||||
}
|
||||
}
|
||||
I::MergeShapes => {
|
||||
@@ -286,14 +293,22 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.circle((15.0, 12.0), 6.5, N, K);
|
||||
}
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_insert_to_design(p: &Pen<'_>, icon: Icon) -> bool {
|
||||
use Icon as I;
|
||||
match icon {
|
||||
// ----- Insert -----
|
||||
I::Table => p.table(2.5, 4.0, 21.5, 20.0, 3, 3, B),
|
||||
I::Table => p.table((2.5, 4.0, 21.5, 20.0), 3, 3, B),
|
||||
I::Picture => p.picture(2.0, 4.0, 22.0, 20.0),
|
||||
I::Screenshot => {
|
||||
for (cx, cy, sx, sy) in [(2.5, 3.5, 1.0, 1.0), (21.5, 3.5, -1.0, 1.0), (2.5, 20.5, 1.0, -1.0), (21.5, 20.5, -1.0, -1.0)] {
|
||||
p.rline(&[(cx, cy + 4.0 * sy), (cx, cy), (cx + 4.0 * sx, cy)], 1.0, K);
|
||||
}
|
||||
p.rect(6.5, 7.5, 17.5, 16.5, 1.0, t(B), B);
|
||||
p.rect((6.5, 7.5, 17.5, 16.5), 1.0, t(B), B);
|
||||
p.seg((6.5, 10.0), (17.5, 10.0), B);
|
||||
}
|
||||
I::Icons => {
|
||||
@@ -320,11 +335,11 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg(mid, bot, K);
|
||||
}
|
||||
I::SmartArt => {
|
||||
p.rect(8.5, 2.5, 15.5, 8.5, 1.0, G, N);
|
||||
p.rect((8.5, 2.5, 15.5, 8.5), 1.0, G, N);
|
||||
p.seg((12.0, 8.5), (12.0, 11.5), K);
|
||||
p.rline(&[(5.5, 14.5), (5.5, 11.5), (18.5, 11.5), (18.5, 14.5)], 1.0, K);
|
||||
p.rect(2.0, 14.5, 9.0, 20.5, 1.0, B, N);
|
||||
p.rect(15.0, 14.5, 22.0, 20.5, 1.0, O, N);
|
||||
p.rect((2.0, 14.5, 9.0, 20.5), 1.0, B, N);
|
||||
p.rect((15.0, 14.5, 22.0, 20.5), 1.0, O, N);
|
||||
}
|
||||
I::Chart => p.bars(2.5, 4.0, 21.5, 20.5),
|
||||
I::Zoom => {
|
||||
@@ -341,7 +356,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
link((15.5, 8.5), B);
|
||||
}
|
||||
I::Action => {
|
||||
p.rect(2.5, 3.5, 18.0, 12.5, 2.0, t(B), B);
|
||||
p.rect((2.5, 3.5, 18.0, 12.5), 2.0, t(B), B);
|
||||
p.cursor(11.5, 8.5, 0.82);
|
||||
}
|
||||
I::Comment => {
|
||||
@@ -350,17 +365,17 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg((6.5, 12.0), (14.0, 12.0), K);
|
||||
}
|
||||
I::TextBox => {
|
||||
p.rect(3.5, 4.5, 20.5, 19.5, 0.5, N, K);
|
||||
p.rect((3.5, 4.5, 20.5, 19.5), 0.5, N, K);
|
||||
for (x, y) in [(3.5, 4.5), (20.5, 4.5), (3.5, 19.5), (20.5, 19.5)] {
|
||||
p.rect(x - 1.5, y - 1.5, x + 1.5, y + 1.5, 0.3, B, N);
|
||||
p.rect((x - 1.5, y - 1.5, x + 1.5, y + 1.5), 0.3, B, N);
|
||||
}
|
||||
p.seg((7.5, 9.5), (16.5, 9.5), K);
|
||||
p.seg((7.5, 14.5), (13.5, 14.5), K);
|
||||
}
|
||||
I::HeaderFooter => {
|
||||
p.rect(4.0, 2.5, 20.0, 21.5, 1.2, N, K);
|
||||
p.rect(6.5, 5.0, 17.5, 7.5, 0.6, B, N);
|
||||
p.rect(6.5, 16.5, 17.5, 19.0, 0.6, B, N);
|
||||
p.rect((4.0, 2.5, 20.0, 21.5), 1.2, N, K);
|
||||
p.rect((6.5, 5.0, 17.5, 7.5), 0.6, B, N);
|
||||
p.rect((6.5, 16.5, 17.5, 19.0), 0.6, B, N);
|
||||
p.seg_w((7.0, 10.5), (17.0, 10.5), K.a(0.45), 0.8);
|
||||
p.seg_w((7.0, 13.2), (14.5, 13.2), K.a(0.45), 0.8);
|
||||
}
|
||||
@@ -370,9 +385,9 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.text_rot("A", (11.3, 11.5), 22.0, O, -10.0);
|
||||
}
|
||||
I::DateTime => {
|
||||
p.rect(3.0, 5.0, 21.0, 21.0, 1.6, N, K);
|
||||
p.rect(3.0, 5.0, 21.0, 9.5, 1.6, R, N);
|
||||
p.rect(3.0, 5.0, 21.0, 21.0, 1.6, N, K);
|
||||
p.rect((3.0, 5.0, 21.0, 21.0), 1.6, N, K);
|
||||
p.rect((3.0, 5.0, 21.0, 9.5), 1.6, R, N);
|
||||
p.rect((3.0, 5.0, 21.0, 21.0), 1.6, N, K);
|
||||
p.seg((8.0, 2.8), (8.0, 6.5), K);
|
||||
p.seg((16.0, 2.8), (16.0, 6.5), K);
|
||||
for (i, (x, y)) in [(8.0, 13.5), (12.0, 13.5), (16.0, 13.5), (8.0, 17.5), (12.0, 17.5), (16.0, 17.5)].into_iter().enumerate() {
|
||||
@@ -388,15 +403,15 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Object => {
|
||||
p.page(3.0, 2.0, 16.0, 19.0, N, K);
|
||||
p.rect(11.5, 11.5, 21.5, 21.5, 1.6, B, N);
|
||||
p.rect(14.2, 14.2, 18.8, 18.8, 0.6, N, W);
|
||||
p.rect((11.5, 11.5, 21.5, 21.5), 1.6, B, N);
|
||||
p.rect((14.2, 14.2, 18.8, 18.8), 0.6, N, W);
|
||||
}
|
||||
I::Equation => {
|
||||
p.line_w(&[(18.5, 6.5), (18.0, 4.0), (6.0, 4.0), (12.5, 12.0), (6.0, 20.0), (18.0, 20.0), (18.5, 17.5)], B, 1.15);
|
||||
}
|
||||
I::Symbol => p.text("Ω", (12.0, 12.0), 22.0, K),
|
||||
I::Video => {
|
||||
p.rect(2.0, 6.0, 15.5, 18.0, 2.0, t(B), K);
|
||||
p.rect((2.0, 6.0, 15.5, 18.0), 2.0, t(B), K);
|
||||
p.rshape(&[(16.5, 10.5), (22.0, 7.0), (22.0, 17.0), (16.5, 13.5)], 0.6, N, K);
|
||||
p.dot((6.0, 10.0), 1.2, R);
|
||||
}
|
||||
@@ -418,16 +433,16 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
|
||||
// ----- Draw tab -----
|
||||
I::Pen => {
|
||||
p.tool((3.5, 20.5), -45.0, 23.0, 2.2, 5.0, B, K, 3.5, K);
|
||||
p.tool(((3.5, 20.5), -45.0), (23.0, 2.2, 5.0), B, K, (3.5, K));
|
||||
}
|
||||
I::Pencil => {
|
||||
p.tool((3.5, 20.5), -45.0, 22.0, 2.6, 5.0, Y, K, 3.5, R);
|
||||
p.tool(((3.5, 20.5), -45.0), (22.0, 2.6, 5.0), Y, K, (3.5, R));
|
||||
}
|
||||
I::HighlighterPen => {
|
||||
let m = |pts: &[P2]| tf((6.0, 15.0), -45.0, pts);
|
||||
p.poly(&m(&[(0.0, -1.0), (3.0, -3.2), (3.0, 3.2), (0.0, 2.2)]), Y, K);
|
||||
p.poly(&m(&[(3.0, -3.6), (14.5, -3.6), (14.5, 3.6), (3.0, 3.6)]), t(Y), K);
|
||||
p.rect(2.0, 19.5, 13.0, 22.5, 0.8, Y, N);
|
||||
p.rect((2.0, 19.5, 13.0, 22.5), 0.8, Y, N);
|
||||
}
|
||||
I::Eraser => {
|
||||
let m = |pts: &[P2]| tf((12.5, 11.0), -45.0, pts);
|
||||
@@ -465,21 +480,21 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
|
||||
// ----- Design -----
|
||||
I::Themes => {
|
||||
p.rect(9.5, 2.5, 21.5, 14.5, 2.0, O, N);
|
||||
p.rect(2.5, 8.5, 15.0, 21.5, 2.0, B, N);
|
||||
p.rect((9.5, 2.5, 21.5, 14.5), 2.0, O, N);
|
||||
p.rect((2.5, 8.5, 15.0, 21.5), 2.0, B, N);
|
||||
p.text("Aa", (8.75, 15.0), 10.0, W);
|
||||
}
|
||||
I::Variants => {
|
||||
p.rect(8.0, 2.5, 21.5, 11.0, 1.2, t(PU), PU);
|
||||
p.rect(5.0, 7.0, 18.5, 15.5, 1.2, t(G), G);
|
||||
p.rect(2.5, 12.0, 16.0, 21.0, 1.2, B, B);
|
||||
p.rect((8.0, 2.5, 21.5, 11.0), 1.2, t(PU), PU);
|
||||
p.rect((5.0, 7.0, 18.5, 15.5), 1.2, t(G), G);
|
||||
p.rect((2.5, 12.0, 16.0, 21.0), 1.2, B, B);
|
||||
}
|
||||
I::Colors => {
|
||||
for (i, c) in [R, Y, G, B].into_iter().enumerate() {
|
||||
let x = 3.0 + i as f32 * 4.5;
|
||||
p.rect(x, 4.0, x + 4.5, 20.0, 0.0, c, N);
|
||||
p.rect((x, 4.0, x + 4.5, 20.0), 0.0, c, N);
|
||||
}
|
||||
p.rect(3.0, 4.0, 21.0, 20.0, 0.0, N, K);
|
||||
p.rect((3.0, 4.0, 21.0, 20.0), 0.0, N, K);
|
||||
}
|
||||
I::Fonts => {
|
||||
p.text("A", (8.0, 11.0), 20.0, K);
|
||||
@@ -496,7 +511,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::FormatBackground => {
|
||||
p.rline(&[(10.0, 17.0), (2.0, 17.0), (2.0, 4.0), (20.0, 4.0), (20.0, 9.0)], 1.5, K);
|
||||
p.rect(4.5, 6.5, 13.5, 12.5, 0.6, t(PU), N);
|
||||
p.rect((4.5, 6.5, 13.5, 12.5), 0.6, t(PU), N);
|
||||
p.brush((12.0, 20.5), -45.0, 13.0, Y);
|
||||
}
|
||||
I::DesignIdeas => {
|
||||
@@ -511,13 +526,21 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
}
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_motion_to_view(p: &Pen<'_>, icon: Icon) -> bool {
|
||||
use Icon as I;
|
||||
match icon {
|
||||
// ----- Transitions / animations -----
|
||||
I::Preview => {
|
||||
p.circle((12.0, 12.0), 9.5, N, K);
|
||||
p.rshape(&[(9.5, 7.0), (17.0, 12.0), (9.5, 17.0)], 0.8, G, N);
|
||||
}
|
||||
I::EffectOptions => {
|
||||
p.rect(2.5, 8.5, 15.5, 21.5, 1.5, t(B), K);
|
||||
p.rect((2.5, 8.5, 15.5, 21.5), 1.5, t(B), K);
|
||||
p.arrow((9.0, 15.0), (21.5, 2.5), B);
|
||||
}
|
||||
I::Sound => {
|
||||
@@ -533,14 +556,14 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Duration => {
|
||||
p.circle((12.0, 13.5), 8.0, N, K);
|
||||
p.rect(10.0, 2.0, 14.0, 4.0, 0.6, K, N);
|
||||
p.rect((10.0, 2.0, 14.0, 4.0), 0.6, K, N);
|
||||
p.seg((12.0, 4.0), (12.0, 5.5), K);
|
||||
p.seg((12.0, 13.5), (15.8, 9.0), B);
|
||||
p.dot((12.0, 13.5), 1.3, B);
|
||||
}
|
||||
I::ApplyToAll => {
|
||||
p.rline(&[(6.0, 7.5), (6.0, 3.0), (21.5, 3.0), (21.5, 13.5), (17.5, 13.5)], 1.5, K);
|
||||
p.rect(2.5, 7.5, 17.5, 18.0, 1.5, N, K);
|
||||
p.rect((2.5, 7.5, 17.5, 18.0), 1.5, N, K);
|
||||
p.badge((18.0, 18.0), 4.8, G, Glyph::Check);
|
||||
}
|
||||
I::AnimEntrance => {
|
||||
@@ -563,9 +586,9 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.circle((4.5, 19.5), 2.4, G, N);
|
||||
}
|
||||
I::AnimationPane => {
|
||||
p.rect(2.5, 3.0, 21.5, 21.0, 1.5, N, K);
|
||||
p.rect((2.5, 3.0, 21.5, 21.0), 1.5, N, K);
|
||||
p.seg((11.0, 3.0), (11.0, 21.0), K);
|
||||
p.rect(4.5, 7.0, 8.5, 12.0, 0.5, t(B), N);
|
||||
p.rect((4.5, 7.0, 8.5, 12.0), 0.5, t(B), N);
|
||||
for (y, c) in [(7.5, G), (12.0, Y), (16.5, R)] {
|
||||
p.seg_w((13.5, y), (19.0, y), c, 1.4);
|
||||
}
|
||||
@@ -585,7 +608,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
// ----- Slide show -----
|
||||
I::PlayFromStart => {
|
||||
p.frame(2.0, 4.0, 22.0, 20.0);
|
||||
p.rect(7.0, 8.0, 9.2, 16.0, 0.5, G, N);
|
||||
p.rect((7.0, 8.0, 9.2, 16.0), 0.5, G, N);
|
||||
p.rshape(&[(11.0, 8.0), (17.5, 12.0), (11.0, 16.0)], 0.6, G, N);
|
||||
}
|
||||
I::PlayFromCurrent => {
|
||||
@@ -594,15 +617,15 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::PresenterView => {
|
||||
p.monitor(2.0, 3.0, 22.0, 16.5);
|
||||
p.rect(4.5, 5.5, 13.0, 12.0, 0.6, t(B), B);
|
||||
p.rect((4.5, 5.5, 13.0, 12.0), 0.6, t(B), B);
|
||||
p.seg_w((15.0, 6.5), (19.5, 6.5), K, 0.8);
|
||||
p.seg_w((15.0, 9.5), (19.5, 9.5), K, 0.8);
|
||||
p.seg_w((15.0, 12.5), (18.0, 12.5), K, 0.8);
|
||||
}
|
||||
I::CustomShow => {
|
||||
p.rect(2.5, 2.5, 12.5, 8.0, 1.0, N, K);
|
||||
p.rect(2.5, 9.5, 12.5, 15.0, 1.0, O, O);
|
||||
p.rect(2.5, 16.5, 12.5, 22.0, 1.0, N, K);
|
||||
p.rect((2.5, 2.5, 12.5, 8.0), 1.0, N, K);
|
||||
p.rect((2.5, 9.5, 12.5, 15.0), 1.0, O, O);
|
||||
p.rect((2.5, 16.5, 12.5, 22.0), 1.0, N, K);
|
||||
p.rshape(&[(15.5, 8.0), (21.5, 12.25), (15.5, 16.5)], 0.6, G, N);
|
||||
}
|
||||
I::SetUpShow => {
|
||||
@@ -620,7 +643,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.circle((12.0, 12.0), 5.5, R, N);
|
||||
}
|
||||
I::Subtitles => {
|
||||
p.rect(2.0, 4.5, 22.0, 19.5, 2.0, N, K);
|
||||
p.rect((2.0, 4.5, 22.0, 19.5), 2.0, N, K);
|
||||
p.seg((5.5, 12.0), (11.0, 12.0), B);
|
||||
p.seg((13.5, 12.0), (18.5, 12.0), B);
|
||||
p.seg((5.5, 16.0), (8.5, 16.0), B);
|
||||
@@ -639,8 +662,8 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.line_w(&[(6.0, 16.0), (10.0, 20.0), (19.5, 11.0)], G, 1.3);
|
||||
}
|
||||
I::Thesaurus => {
|
||||
p.rect(8.0, 2.5, 19.5, 21.5, 1.2, t(B), N);
|
||||
p.rect(4.5, 2.5, 19.5, 21.5, 1.5, N, K);
|
||||
p.rect((8.0, 2.5, 19.5, 21.5), 1.2, t(B), N);
|
||||
p.rect((4.5, 2.5, 19.5, 21.5), 1.5, N, K);
|
||||
p.seg((8.0, 2.5), (8.0, 21.5), K);
|
||||
p.poly(&[(13.5, 2.5), (17.0, 2.5), (17.0, 9.0), (15.25, 7.5), (13.5, 9.0)], R, N);
|
||||
}
|
||||
@@ -652,8 +675,8 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.line(&[(9.0, 18.5), (12.0, 13.8), (15.0, 18.5)], B);
|
||||
}
|
||||
I::Translate => {
|
||||
p.rect(10.0, 9.0, 21.5, 20.5, 2.0, O, N);
|
||||
p.rect(2.5, 3.5, 14.0, 15.0, 2.0, B, N);
|
||||
p.rect((10.0, 9.0, 21.5, 20.5), 2.0, O, N);
|
||||
p.rect((2.5, 3.5, 14.0, 15.0), 2.0, B, N);
|
||||
p.letter_a(5.5, 5.8, 11.0, 12.8, W, 0.9);
|
||||
p.seg_w((17.75, 10.8), (17.75, 11.9), W, 0.8);
|
||||
p.seg_w((15.2, 12.9), (20.3, 12.9), W, 0.8);
|
||||
@@ -690,8 +713,8 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg((5.5, 14.2), (11.0, 14.2), K);
|
||||
}
|
||||
I::Compare => {
|
||||
p.rect(2.5, 2.5, 11.0, 15.5, 1.2, N, K);
|
||||
p.rect(13.0, 8.5, 21.5, 21.5, 1.2, t(B), B);
|
||||
p.rect((2.5, 2.5, 11.0, 15.5), 1.2, N, K);
|
||||
p.rect((13.0, 8.5, 21.5, 21.5), 1.2, t(B), B);
|
||||
p.arrow((13.0, 4.0), (21.5, 4.0), K);
|
||||
p.arrow((11.0, 20.0), (2.5, 20.0), K);
|
||||
}
|
||||
@@ -702,37 +725,37 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
|
||||
// ----- View -----
|
||||
I::ViewNormal => {
|
||||
p.rect(2.0, 3.5, 22.0, 20.5, 1.5, N, K);
|
||||
p.rect((2.0, 3.5, 22.0, 20.5), 1.5, N, K);
|
||||
p.seg((8.0, 3.5), (8.0, 20.5), K);
|
||||
for y in [6.0, 10.5, 15.0] {
|
||||
p.rect(3.9, y, 6.2, y + 2.6, 0.3, K.a(0.55), N);
|
||||
p.rect((3.9, y, 6.2, y + 2.6), 0.3, K.a(0.55), N);
|
||||
}
|
||||
p.rect(10.0, 6.0, 20.0, 15.0, 0.6, t(B), B);
|
||||
p.rect((10.0, 6.0, 20.0, 15.0), 0.6, t(B), B);
|
||||
p.seg_w((10.0, 18.0), (17.0, 18.0), K.a(0.5), 0.8);
|
||||
}
|
||||
I::ViewOutline => {
|
||||
p.rect(2.0, 3.5, 22.0, 20.5, 1.5, N, K);
|
||||
p.rect((2.0, 3.5, 22.0, 20.5), 1.5, N, K);
|
||||
p.seg((11.0, 3.5), (11.0, 20.5), K);
|
||||
p.seg_w((4.0, 7.0), (9.0, 7.0), K, 0.8);
|
||||
p.seg_w((5.5, 10.0), (9.0, 10.0), K, 0.8);
|
||||
p.seg_w((5.5, 13.0), (9.0, 13.0), K, 0.8);
|
||||
p.seg_w((4.0, 16.0), (9.0, 16.0), K, 0.8);
|
||||
p.rect(13.0, 7.0, 20.0, 13.0, 0.6, t(B), B);
|
||||
p.rect((13.0, 7.0, 20.0, 13.0), 0.6, t(B), B);
|
||||
}
|
||||
I::ViewSorter => {
|
||||
for (i, (x0, x1)) in [(2.5, 11.0), (13.0, 21.5)].into_iter().enumerate() {
|
||||
for (j, (y0, y1)) in [(4.0, 10.5), (13.5, 20.0)].into_iter().enumerate() {
|
||||
if i == 0 && j == 0 {
|
||||
p.rect(x0, y0, x1, y1, 0.8, t(B), B);
|
||||
p.rect((x0, y0, x1, y1), 0.8, t(B), B);
|
||||
} else {
|
||||
p.rect(x0, y0, x1, y1, 0.8, N, K);
|
||||
p.rect((x0, y0, x1, y1), 0.8, N, K);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
I::ViewNotesPage => {
|
||||
p.rect(5.0, 2.0, 19.0, 22.0, 1.2, N, K);
|
||||
p.rect(7.5, 4.5, 16.5, 10.5, 0.6, t(B), B);
|
||||
p.rect((5.0, 2.0, 19.0, 22.0), 1.2, N, K);
|
||||
p.rect((7.5, 4.5, 16.5, 10.5), 0.6, t(B), B);
|
||||
p.seg_w((7.5, 14.0), (16.5, 14.0), K, 0.8);
|
||||
p.seg_w((7.5, 17.5), (14.0, 17.5), K, 0.8);
|
||||
}
|
||||
@@ -741,24 +764,24 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg((12.0, 7.0), (12.0, 20.5), K);
|
||||
}
|
||||
I::SlideMaster => {
|
||||
p.rect(2.0, 2.5, 15.0, 10.5, 1.2, t(PU), PU);
|
||||
p.rect((2.0, 2.5, 15.0, 10.5), 1.2, t(PU), PU);
|
||||
p.seg((4.5, 5.5), (11.0, 5.5), PU);
|
||||
p.rline(&[(5.0, 10.5), (5.0, 19.5), (9.0, 19.5)], 1.0, K);
|
||||
p.seg((5.0, 14.0), (9.0, 14.0), K);
|
||||
p.rect(9.0, 12.0, 17.0, 16.0, 0.8, N, K);
|
||||
p.rect(9.0, 17.5, 17.0, 21.5, 0.8, N, K);
|
||||
p.rect((9.0, 12.0, 17.0, 16.0), 0.8, N, K);
|
||||
p.rect((9.0, 17.5, 17.0, 21.5), 0.8, N, K);
|
||||
}
|
||||
I::HandoutMaster => {
|
||||
p.rect(4.0, 2.0, 20.0, 22.0, 1.2, N, K);
|
||||
p.rect((4.0, 2.0, 20.0, 22.0), 1.2, N, K);
|
||||
for (x, y) in [(6.5, 4.5), (12.75, 4.5), (6.5, 11.0), (12.75, 11.0)] {
|
||||
p.rect(x, y, x + 4.75, y + 4.5, 0.4, t(PU), PU);
|
||||
p.rect((x, y, x + 4.75, y + 4.5), 0.4, t(PU), PU);
|
||||
}
|
||||
p.seg_w((6.5, 19.0), (17.5, 19.0), K, 0.8);
|
||||
}
|
||||
I::NotesMaster => {
|
||||
p.rline(&[(8.0, 4.0), (8.0, 2.0), (21.0, 2.0), (21.0, 19.0), (16.0, 19.0)], 1.2, PU);
|
||||
p.rect(3.0, 4.5, 16.0, 22.0, 1.2, N, K);
|
||||
p.rect(5.5, 7.0, 13.5, 12.0, 0.5, t(PU), PU);
|
||||
p.rect((3.0, 4.5, 16.0, 22.0), 1.2, N, K);
|
||||
p.rect((5.5, 7.0, 13.5, 12.0), 0.5, t(PU), PU);
|
||||
p.seg_w((5.5, 15.5), (13.5, 15.5), K, 0.8);
|
||||
p.seg_w((5.5, 18.8), (11.5, 18.8), K, 0.8);
|
||||
}
|
||||
@@ -767,15 +790,15 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg_w((v, 3.0), (v, 21.0), K.a(0.6), 0.7);
|
||||
p.seg_w((3.0, v), (21.0, v), K.a(0.6), 0.7);
|
||||
}
|
||||
p.rect(3.0, 3.0, 21.0, 21.0, 1.2, N, K);
|
||||
p.rect((3.0, 3.0, 21.0, 21.0), 1.2, N, K);
|
||||
}
|
||||
I::Guides => {
|
||||
p.rect(3.0, 3.0, 21.0, 21.0, 1.2, N, K);
|
||||
p.rect((3.0, 3.0, 21.0, 21.0), 1.2, N, K);
|
||||
p.dashed(&[(12.0, 1.5), (12.0, 22.5)], B, 2.2, 1.6);
|
||||
p.dashed(&[(1.5, 12.0), (22.5, 12.0)], B, 2.2, 1.6);
|
||||
}
|
||||
I::Notes => {
|
||||
p.rect(4.0, 4.0, 20.0, 21.5, 1.5, t(Y), K);
|
||||
p.rect((4.0, 4.0, 20.0, 21.5), 1.5, t(Y), K);
|
||||
for x in [8.0, 12.0, 16.0] {
|
||||
p.seg((x, 2.3), (x, 5.8), K);
|
||||
}
|
||||
@@ -788,16 +811,16 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
for (cx, cy, sx, sy) in [(2.5, 2.5, 1.0, 1.0), (21.5, 2.5, -1.0, 1.0), (2.5, 21.5, 1.0, -1.0), (21.5, 21.5, -1.0, -1.0)] {
|
||||
p.rline(&[(cx, cy + 5.0 * sy), (cx, cy), (cx + 5.0 * sx, cy)], 0.8, K);
|
||||
}
|
||||
p.rect(7.0, 8.0, 17.0, 16.0, 1.0, t(B), B);
|
||||
p.rect((7.0, 8.0, 17.0, 16.0), 1.0, t(B), B);
|
||||
}
|
||||
I::Grayscale => {
|
||||
p.rect(3.0, 3.0, 9.0, 21.0, 0.0, K.a(0.85), N);
|
||||
p.rect(9.0, 3.0, 15.0, 21.0, 0.0, K.a(0.45), N);
|
||||
p.rect(15.0, 3.0, 21.0, 21.0, 0.0, K.a(0.15), N);
|
||||
p.rect(3.0, 3.0, 21.0, 21.0, 1.2, N, K);
|
||||
p.rect((3.0, 3.0, 9.0, 21.0), 0.0, K.a(0.85), N);
|
||||
p.rect((9.0, 3.0, 15.0, 21.0), 0.0, K.a(0.45), N);
|
||||
p.rect((15.0, 3.0, 21.0, 21.0), 0.0, K.a(0.15), N);
|
||||
p.rect((3.0, 3.0, 21.0, 21.0), 1.2, N, K);
|
||||
}
|
||||
I::NewWindow => {
|
||||
p.rect(2.0, 3.5, 20.0, 17.0, 1.5, N, K);
|
||||
p.rect((2.0, 3.5, 20.0, 17.0), 1.5, N, K);
|
||||
p.seg((2.0, 7.0), (20.0, 7.0), K);
|
||||
p.dot((4.5, 5.25), 0.6, K);
|
||||
p.badge((18.5, 18.0), 4.8, G, Glyph::Plus);
|
||||
@@ -809,6 +832,14 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.gear_badge((17.5, 17.5), 5.2, O);
|
||||
}
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_arrange_to_media(p: &Pen<'_>, icon: Icon) -> bool {
|
||||
use Icon as I;
|
||||
match icon {
|
||||
// ----- Arrange -----
|
||||
I::BringForward => {
|
||||
p.layer(10.0, 15.5, 8.0, 4.0, N, K);
|
||||
@@ -836,64 +867,64 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Group => {
|
||||
p.dashed(&[(2.5, 2.5), (21.5, 2.5), (21.5, 21.5), (2.5, 21.5), (2.5, 2.5)], B, 2.0, 1.6);
|
||||
p.rect(5.5, 5.5, 13.0, 13.0, 0.8, t(G), G);
|
||||
p.rect((5.5, 5.5, 13.0, 13.0), 0.8, t(G), G);
|
||||
p.circle((15.5, 15.5), 4.3, t(O), O);
|
||||
}
|
||||
I::Ungroup => {
|
||||
p.dashed_w(&[(2.0, 2.0), (12.0, 2.0), (12.0, 12.0), (2.0, 12.0), (2.0, 2.0)], B, 1.6, 1.3, 0.8);
|
||||
p.rect(4.5, 4.5, 9.5, 9.5, 0.6, t(G), G);
|
||||
p.rect((4.5, 4.5, 9.5, 9.5), 0.6, t(G), G);
|
||||
p.dashed_w(&[(12.0, 12.0), (22.0, 12.0), (22.0, 22.0), (12.0, 22.0), (12.0, 12.0)], B, 1.6, 1.3, 0.8);
|
||||
p.circle((17.0, 17.0), 2.9, t(O), O);
|
||||
}
|
||||
I::AlignObjects => {
|
||||
p.dashed(&[(2.0, 12.0), (22.0, 12.0)], K, 2.0, 1.5);
|
||||
p.rect(3.5, 4.0, 8.0, 20.0, 0.8, B, N);
|
||||
p.rect(10.0, 7.5, 14.0, 16.5, 0.8, O, N);
|
||||
p.rect(16.0, 5.5, 20.5, 18.5, 0.8, G, N);
|
||||
p.rect((3.5, 4.0, 8.0, 20.0), 0.8, B, N);
|
||||
p.rect((10.0, 7.5, 14.0, 16.5), 0.8, O, N);
|
||||
p.rect((16.0, 5.5, 20.5, 18.5), 0.8, G, N);
|
||||
}
|
||||
I::AlignObjLeft => {
|
||||
p.rect(3.5, 5.0, 19.5, 10.5, 0.8, t(B), B);
|
||||
p.rect(3.5, 13.5, 13.5, 19.0, 0.8, t(B), B);
|
||||
p.rect((3.5, 5.0, 19.5, 10.5), 0.8, t(B), B);
|
||||
p.rect((3.5, 13.5, 13.5, 19.0), 0.8, t(B), B);
|
||||
p.seg((3.5, 2.5), (3.5, 21.5), K);
|
||||
}
|
||||
I::AlignObjCenter => {
|
||||
p.rect(4.0, 5.0, 20.0, 10.5, 0.8, t(B), B);
|
||||
p.rect(7.0, 13.5, 17.0, 19.0, 0.8, t(B), B);
|
||||
p.rect((4.0, 5.0, 20.0, 10.5), 0.8, t(B), B);
|
||||
p.rect((7.0, 13.5, 17.0, 19.0), 0.8, t(B), B);
|
||||
p.seg((12.0, 2.5), (12.0, 21.5), K);
|
||||
}
|
||||
I::AlignObjRight => {
|
||||
p.rect(4.5, 5.0, 20.5, 10.5, 0.8, t(B), B);
|
||||
p.rect(10.5, 13.5, 20.5, 19.0, 0.8, t(B), B);
|
||||
p.rect((4.5, 5.0, 20.5, 10.5), 0.8, t(B), B);
|
||||
p.rect((10.5, 13.5, 20.5, 19.0), 0.8, t(B), B);
|
||||
p.seg((20.5, 2.5), (20.5, 21.5), K);
|
||||
}
|
||||
I::AlignObjTop => {
|
||||
p.rect(5.0, 3.5, 10.5, 19.5, 0.8, t(B), B);
|
||||
p.rect(13.5, 3.5, 19.0, 13.5, 0.8, t(B), B);
|
||||
p.rect((5.0, 3.5, 10.5, 19.5), 0.8, t(B), B);
|
||||
p.rect((13.5, 3.5, 19.0, 13.5), 0.8, t(B), B);
|
||||
p.seg((2.5, 3.5), (21.5, 3.5), K);
|
||||
}
|
||||
I::AlignObjMiddle => {
|
||||
p.rect(5.0, 4.0, 10.5, 20.0, 0.8, t(B), B);
|
||||
p.rect(13.5, 7.0, 19.0, 17.0, 0.8, t(B), B);
|
||||
p.rect((5.0, 4.0, 10.5, 20.0), 0.8, t(B), B);
|
||||
p.rect((13.5, 7.0, 19.0, 17.0), 0.8, t(B), B);
|
||||
p.seg((2.5, 12.0), (21.5, 12.0), K);
|
||||
}
|
||||
I::AlignObjBottom => {
|
||||
p.rect(5.0, 4.5, 10.5, 20.5, 0.8, t(B), B);
|
||||
p.rect(13.5, 10.5, 19.0, 20.5, 0.8, t(B), B);
|
||||
p.rect((5.0, 4.5, 10.5, 20.5), 0.8, t(B), B);
|
||||
p.rect((13.5, 10.5, 19.0, 20.5), 0.8, t(B), B);
|
||||
p.seg((2.5, 20.5), (21.5, 20.5), K);
|
||||
}
|
||||
I::DistributeH => {
|
||||
p.seg((2.5, 3.0), (2.5, 21.0), K);
|
||||
p.seg((21.5, 3.0), (21.5, 21.0), K);
|
||||
p.rect(4.9, 7.0, 8.2, 17.0, 0.6, t(B), B);
|
||||
p.rect(10.35, 5.0, 13.65, 19.0, 0.6, t(B), B);
|
||||
p.rect(15.8, 8.5, 19.1, 15.5, 0.6, t(B), B);
|
||||
p.rect((4.9, 7.0, 8.2, 17.0), 0.6, t(B), B);
|
||||
p.rect((10.35, 5.0, 13.65, 19.0), 0.6, t(B), B);
|
||||
p.rect((15.8, 8.5, 19.1, 15.5), 0.6, t(B), B);
|
||||
}
|
||||
I::DistributeV => {
|
||||
p.seg((3.0, 2.5), (21.0, 2.5), K);
|
||||
p.seg((3.0, 21.5), (21.0, 21.5), K);
|
||||
p.rect(7.0, 4.9, 17.0, 8.2, 0.6, t(B), B);
|
||||
p.rect(5.0, 10.35, 19.0, 13.65, 0.6, t(B), B);
|
||||
p.rect(8.5, 15.8, 15.5, 19.1, 0.6, t(B), B);
|
||||
p.rect((7.0, 4.9, 17.0, 8.2), 0.6, t(B), B);
|
||||
p.rect((5.0, 10.35, 19.0, 13.65), 0.6, t(B), B);
|
||||
p.rect((8.5, 15.8, 15.5, 19.1), 0.6, t(B), B);
|
||||
}
|
||||
I::RotateLeft => {
|
||||
p.poly(&[(3.0, 21.5), (3.0, 13.0), (12.0, 21.5)], t(B), B);
|
||||
@@ -914,15 +945,15 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.poly(&[(5.0, 14.5), (19.0, 21.5), (19.0, 14.5)], B, B);
|
||||
}
|
||||
I::SelectionPane => {
|
||||
p.rect(3.0, 2.5, 21.0, 21.5, 1.5, N, K);
|
||||
p.rect(4.6, 4.6, 19.4, 9.4, 0.8, t(B), N);
|
||||
p.rect((3.0, 2.5, 21.0, 21.5), 1.5, N, K);
|
||||
p.rect((4.6, 4.6, 19.4, 9.4), 0.8, t(B), N);
|
||||
for (y, c) in [(7.0, B), (12.0, O), (17.0, G)] {
|
||||
p.rect(6.0, y - 1.3, 8.6, y + 1.3, 0.4, c, N);
|
||||
p.rect((6.0, y - 1.3, 8.6, y + 1.3), 0.4, c, N);
|
||||
p.seg_w((10.5, y), (17.5, y), K, 0.85);
|
||||
}
|
||||
}
|
||||
I::Rotate => {
|
||||
p.rect(9.0, 9.0, 15.0, 15.0, 0.8, t(B), B);
|
||||
p.rect((9.0, 9.0, 15.0, 15.0), 0.8, t(B), B);
|
||||
p.arc_arrow((12.0, 12.0), 8.5, -70.0, 220.0, K);
|
||||
}
|
||||
|
||||
@@ -945,7 +976,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.circle((15.7, 14.6), 5.6, B.a(0.6), N);
|
||||
}
|
||||
I::ArtisticEffects => {
|
||||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, N, K);
|
||||
p.rect((2.0, 4.0, 22.0, 20.0), 1.5, N, K);
|
||||
p.curve_w((5.5, 16.0), (8.0, 4.5), (14.0, 21.0), (18.5, 8.0), PU, 2.0);
|
||||
}
|
||||
I::Transparency => {
|
||||
@@ -954,12 +985,12 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
for j in 0..4 {
|
||||
if (i + j) % 2 == 0 {
|
||||
let (x, y) = (3.0 + i as f32 * cs, 3.0 + j as f32 * cs);
|
||||
p.rect(x, y, x + cs, y + cs, 0.0, K.a(0.3), N);
|
||||
p.rect((x, y, x + cs, y + cs), 0.0, K.a(0.3), N);
|
||||
}
|
||||
}
|
||||
}
|
||||
p.rect(3.0, 3.0, 16.0, 16.0, 0.6, N, K.a(0.6));
|
||||
p.rect(9.0, 9.0, 21.0, 21.0, 1.2, B.a(0.55), B);
|
||||
p.rect((3.0, 3.0, 16.0, 16.0), 0.6, N, K.a(0.6));
|
||||
p.rect((9.0, 9.0, 21.0, 21.0), 1.2, B.a(0.55), B);
|
||||
}
|
||||
I::CompressPictures => {
|
||||
p.picture(6.5, 6.0, 17.5, 18.0);
|
||||
@@ -983,10 +1014,10 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.arc_arrow((17.0, 17.5), 4.0, -10.0, -260.0, B);
|
||||
}
|
||||
I::RemoveBackground => {
|
||||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, PU.a(0.55), N);
|
||||
p.rect((2.0, 4.0, 22.0, 20.0), 1.5, PU.a(0.55), N);
|
||||
p.dot((12.0, 10.0), 3.0, B);
|
||||
p.poly(&arc_pts((12.0, 19.3), 6.0, 5.5, 180.0, 360.0), B, N);
|
||||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, N, K);
|
||||
p.rect((2.0, 4.0, 22.0, 20.0), 1.5, N, K);
|
||||
}
|
||||
I::AltText => {
|
||||
p.picture(2.0, 2.0, 15.5, 12.5);
|
||||
@@ -1002,24 +1033,24 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
I::InsertLeft => ((10.0, 3.0, 21.0, 21.0, 3, 2), (2.5, 3.0, 7.5, 21.0)),
|
||||
_ => ((3.0, 3.0, 14.0, 21.0, 3, 2), (16.5, 3.0, 21.5, 21.0)),
|
||||
};
|
||||
p.table(grid.0, grid.1, grid.2, grid.3, grid.4, grid.5, N);
|
||||
p.rect(add.0, add.1, add.2, add.3, 1.0, t(G), G);
|
||||
p.table((grid.0, grid.1, grid.2, grid.3), grid.4, grid.5, N);
|
||||
p.rect((add.0, add.1, add.2, add.3), 1.0, t(G), G);
|
||||
let c = ((add.0 + add.2) * 0.5, (add.1 + add.3) * 0.5);
|
||||
p.seg_w((c.0 - 1.6, c.1), (c.0 + 1.6, c.1), G, 0.9);
|
||||
p.seg_w((c.0, c.1 - 1.6), (c.0, c.1 + 1.6), G, 0.9);
|
||||
}
|
||||
I::DeleteTable => {
|
||||
p.table(2.5, 3.0, 19.5, 17.0, 3, 3, B);
|
||||
p.table((2.5, 3.0, 19.5, 17.0), 3, 3, B);
|
||||
p.badge((18.0, 18.0), 5.0, R, Glyph::Cross);
|
||||
}
|
||||
I::MergeCells => {
|
||||
p.rect(2.5, 6.0, 21.5, 18.0, 1.2, N, K);
|
||||
p.rect((2.5, 6.0, 21.5, 18.0), 1.2, N, K);
|
||||
p.dashed(&[(12.0, 6.0), (12.0, 18.0)], K.a(0.6), 1.5, 1.2);
|
||||
p.arrow((4.5, 12.0), (10.2, 12.0), B);
|
||||
p.arrow((19.5, 12.0), (13.8, 12.0), B);
|
||||
}
|
||||
I::SplitCells => {
|
||||
p.rect(2.5, 6.0, 21.5, 18.0, 1.2, N, K);
|
||||
p.rect((2.5, 6.0, 21.5, 18.0), 1.2, N, K);
|
||||
p.seg((12.0, 6.0), (12.0, 18.0), K);
|
||||
p.arrow((10.0, 12.0), (4.5, 12.0), B);
|
||||
p.arrow((14.0, 12.0), (19.5, 12.0), B);
|
||||
@@ -1027,12 +1058,12 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
I::Borders => {
|
||||
p.dotted(&[(12.0, 5.0), (12.0, 19.0)], K, 2.2, 0.45);
|
||||
p.dotted(&[(5.0, 12.0), (19.0, 12.0)], K, 2.2, 0.45);
|
||||
p.rect(3.0, 3.0, 21.0, 21.0, 0.6, N, B);
|
||||
p.rect((3.0, 3.0, 21.0, 21.0), 0.6, N, B);
|
||||
}
|
||||
I::Shading => {
|
||||
p.rect(3.0, 3.0, 12.0, 12.0, 0.0, Y.a(0.8), N);
|
||||
p.rect(12.0, 12.0, 21.0, 21.0, 0.0, Y.a(0.8), N);
|
||||
p.table(3.0, 3.0, 21.0, 21.0, 2, 2, N);
|
||||
p.rect((3.0, 3.0, 12.0, 12.0), 0.0, Y.a(0.8), N);
|
||||
p.rect((12.0, 12.0, 21.0, 21.0), 0.0, Y.a(0.8), N);
|
||||
p.table((3.0, 3.0, 21.0, 21.0), 2, 2, N);
|
||||
}
|
||||
|
||||
// ----- Chart -----
|
||||
@@ -1045,40 +1076,40 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.brush((14.0, 10.0), -45.0, 12.0, PU);
|
||||
}
|
||||
I::SwitchRowCol => {
|
||||
p.table(2.5, 2.5, 11.0, 11.0, 2, 2, B);
|
||||
p.rect(13.0, 13.0, 17.25, 21.5, 0.6, O, N);
|
||||
p.table(13.0, 13.0, 21.5, 21.5, 2, 2, N);
|
||||
p.table((2.5, 2.5, 11.0, 11.0), 2, 2, B);
|
||||
p.rect((13.0, 13.0, 17.25, 21.5), 0.6, O, N);
|
||||
p.table((13.0, 13.0, 21.5, 21.5), 2, 2, N);
|
||||
p.line(&quad_pts((14.0, 4.0), (20.0, 4.0), (20.0, 9.5), 10), K);
|
||||
p.head((20.0, 11.0), 90.0, 3.6, K);
|
||||
p.line(&quad_pts((10.0, 20.0), (4.0, 20.0), (4.0, 14.5), 10), K);
|
||||
p.head((4.0, 13.0), -90.0, 3.6, K);
|
||||
}
|
||||
I::EditData => {
|
||||
p.table(2.5, 3.0, 18.0, 16.0, 3, 3, B);
|
||||
p.tool((11.0, 21.5), -45.0, 14.0, 2.2, 3.8, Y, K, 2.5, R);
|
||||
p.table((2.5, 3.0, 18.0, 16.0), 3, 3, B);
|
||||
p.tool(((11.0, 21.5), -45.0), (14.0, 2.2, 3.8), Y, K, (2.5, R));
|
||||
}
|
||||
I::ChangeChartType => {
|
||||
p.circle((8.0, 8.0), 5.8, t(B), B);
|
||||
let mut wedge = vec![(8.0, 8.0)];
|
||||
wedge.extend(arc_pts((8.0, 8.0), 5.8, 5.8, -90.0, 0.0));
|
||||
p.poly(&wedge, O, N);
|
||||
p.rect(13.0, 15.0, 15.8, 21.0, 0.4, G, N);
|
||||
p.rect(17.5, 11.0, 20.3, 21.0, 0.4, B, N);
|
||||
p.rect((13.0, 15.0, 15.8, 21.0), 0.4, G, N);
|
||||
p.rect((17.5, 11.0, 20.3, 21.0), 0.4, B, N);
|
||||
p.seg((11.5, 21.5), (22.0, 21.5), K);
|
||||
}
|
||||
|
||||
// ----- Media -----
|
||||
I::Play => p.rshape(&[(6.5, 4.0), (19.5, 12.0), (6.5, 20.0)], 1.0, G, N),
|
||||
I::Pause => {
|
||||
p.rect(6.0, 4.5, 10.0, 19.5, 1.0, K, N);
|
||||
p.rect(14.0, 4.5, 18.0, 19.5, 1.0, K, N);
|
||||
p.rect((6.0, 4.5, 10.0, 19.5), 1.0, K, N);
|
||||
p.rect((14.0, 4.5, 18.0, 19.5), 1.0, K, N);
|
||||
}
|
||||
I::Stop => p.rect(5.0, 5.0, 19.0, 19.0, 2.0, K, N),
|
||||
I::Stop => p.rect((5.0, 5.0, 19.0, 19.0), 2.0, K, N),
|
||||
I::TrimMedia => {
|
||||
p.rect(2.0, 8.0, 22.0, 16.0, 1.2, N, K);
|
||||
p.rect(7.0, 8.0, 17.0, 16.0, 0.0, t(Y), N);
|
||||
p.rect(5.8, 5.0, 8.2, 19.0, 0.8, Y, N);
|
||||
p.rect(15.8, 5.0, 18.2, 19.0, 0.8, Y, N);
|
||||
p.rect((2.0, 8.0, 22.0, 16.0), 1.2, N, K);
|
||||
p.rect((7.0, 8.0, 17.0, 16.0), 0.0, t(Y), N);
|
||||
p.rect((5.8, 5.0, 8.2, 19.0), 0.8, Y, N);
|
||||
p.rect((15.8, 5.0, 18.2, 19.0), 0.8, Y, N);
|
||||
}
|
||||
I::Volume => {
|
||||
p.speaker(2.5, 12.0, N, K);
|
||||
@@ -1094,17 +1125,25 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.rshape(&[(6.0, 2.5), (18.0, 2.5), (18.0, 21.5), (12.0, 16.5), (6.0, 21.5)], 0.8, R, N);
|
||||
}
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_chrome_and_status(p: &Pen<'_>, icon: Icon) -> bool {
|
||||
use Icon as I;
|
||||
match icon {
|
||||
// ----- Window chrome -----
|
||||
I::Home => {
|
||||
p.rect(10.0, 14.5, 14.0, 21.0, 0.8, B, N);
|
||||
p.rect((10.0, 14.5, 14.0, 21.0), 0.8, B, N);
|
||||
p.line(&[(2.5, 11.5), (12.0, 3.0), (21.5, 11.5)], K);
|
||||
p.rline(&[(5.0, 9.5), (5.0, 21.0), (19.0, 21.0), (19.0, 9.5)], 1.2, K);
|
||||
}
|
||||
I::Save => {
|
||||
p.rshape(&[(3.0, 3.0), (17.5, 3.0), (21.0, 6.5), (21.0, 21.0), (3.0, 21.0)], 1.5, t(B), B);
|
||||
p.rect(7.0, 3.0, 15.0, 8.5, 0.6, N, B);
|
||||
p.rect((7.0, 3.0, 15.0, 8.5), 0.6, N, B);
|
||||
p.seg((12.5, 4.5), (12.5, 7.0), B);
|
||||
p.rect(6.5, 13.0, 17.5, 21.0, 0.8, N, B);
|
||||
p.rect((6.5, 13.0, 17.5, 21.0), 0.8, N, B);
|
||||
}
|
||||
I::Undo | I::Redo => {
|
||||
let mut pts = vec![(6.0, 8.5), (14.0, 8.5)];
|
||||
@@ -1147,9 +1186,9 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.arrow((8.5, 12.0), (22.0, 12.0), B);
|
||||
}
|
||||
I::Print => {
|
||||
p.rect(6.5, 2.5, 17.5, 9.0, 0.8, N, K);
|
||||
p.rect((6.5, 2.5, 17.5, 9.0), 0.8, N, K);
|
||||
p.rline(&[(6.5, 17.5), (2.5, 17.5), (2.5, 9.0), (21.5, 9.0), (21.5, 17.5), (17.5, 17.5)], 1.8, K);
|
||||
p.rect(6.5, 14.0, 17.5, 21.5, 0.8, N, K);
|
||||
p.rect((6.5, 14.0, 17.5, 21.5), 0.8, N, K);
|
||||
p.dot((18.5, 12.0), 1.1, G);
|
||||
}
|
||||
I::Settings => {
|
||||
@@ -1180,7 +1219,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
sh.extend(arc_pts((12.0, 8.0), 4.5, 4.5, 180.0, 360.0));
|
||||
sh.push((16.5, 10.5));
|
||||
p.line(&sh, K);
|
||||
p.rect(4.5, 10.5, 19.5, 21.5, 1.8, N, K);
|
||||
p.rect((4.5, 10.5, 19.5, 21.5), 1.8, N, K);
|
||||
p.seg((12.0, 14.5), (12.0, 17.5), K);
|
||||
}
|
||||
I::Unlock => {
|
||||
@@ -1189,7 +1228,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
sh.extend(arc_pts((16.5, 6.0), 4.5, 4.5, 180.0, 360.0));
|
||||
sh.push((21.0, 8.5));
|
||||
p.line(&sh, K);
|
||||
p.rect(3.0, 10.5, 16.5, 21.5, 1.8, N, K);
|
||||
p.rect((3.0, 10.5, 16.5, 21.5), 1.8, N, K);
|
||||
p.seg((9.75, 14.5), (9.75, 17.5), K);
|
||||
}
|
||||
I::Eye | I::EyeOff => {
|
||||
@@ -1225,7 +1264,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Star => p.star((12.0, 12.8), 10.0, t(Y), Y),
|
||||
I::Image => {
|
||||
p.rect(2.5, 4.0, 21.5, 20.0, 1.5, N, K);
|
||||
p.rect((2.5, 4.0, 21.5, 20.0), 1.5, N, K);
|
||||
p.circle((16.0, 9.0), 1.8, N, K);
|
||||
p.line(&[(2.5, 17.5), (8.5, 11.0), (14.0, 16.5), (16.5, 14.0), (21.5, 18.5)], K);
|
||||
}
|
||||
@@ -1244,7 +1283,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::Grid => {
|
||||
for (x, y) in [(3.0, 3.0), (13.0, 3.0), (3.0, 13.0), (13.0, 13.0)] {
|
||||
p.rect(x, y, x + 8.0, y + 8.0, 1.5, N, K);
|
||||
p.rect((x, y, x + 8.0, y + 8.0), 1.5, N, K);
|
||||
}
|
||||
}
|
||||
I::List => {
|
||||
@@ -1278,7 +1317,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::SaveAs => {
|
||||
p.rline(&[(11.0, 20.5), (2.5, 20.5), (2.5, 3.0), (15.0, 3.0), (18.0, 6.0), (18.0, 10.5)], 1.2, K);
|
||||
p.rect(6.0, 3.0, 13.0, 7.5, 0.6, N, K);
|
||||
p.rect((6.0, 3.0, 13.0, 7.5), 0.6, N, K);
|
||||
p.seg_w((13.5, 21.0), (21.5, 13.0), O, 1.4);
|
||||
p.seg_w((12.5, 22.0), (13.5, 21.0), K, 1.0);
|
||||
}
|
||||
@@ -1300,18 +1339,18 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
|
||||
// ----- Status bar (monochrome) -----
|
||||
I::NotesSmall => {
|
||||
p.rect(3.0, 4.0, 21.0, 20.0, 1.5, N, K);
|
||||
p.rect((3.0, 4.0, 21.0, 20.0), 1.5, N, K);
|
||||
p.seg((3.0, 13.5), (21.0, 13.5), K);
|
||||
p.seg((6.5, 16.8), (15.5, 16.8), K);
|
||||
}
|
||||
I::CommentsSmall => p.bubble(3.0, 4.0, 21.0, 16.5, N, K),
|
||||
I::ViewNormalSmall => {
|
||||
p.rect(2.5, 4.0, 21.5, 20.0, 1.5, N, K);
|
||||
p.rect((2.5, 4.0, 21.5, 20.0), 1.5, N, K);
|
||||
p.seg((8.5, 4.0), (8.5, 20.0), K);
|
||||
}
|
||||
I::ViewSorterSmall => {
|
||||
for (x, y) in [(2.5, 4.5), (13.0, 4.5), (2.5, 13.5), (13.0, 13.5)] {
|
||||
p.rect(x, y, x + 8.5, y + 6.0, 1.0, N, K);
|
||||
p.rect((x, y, x + 8.5, y + 6.0), 1.0, N, K);
|
||||
}
|
||||
}
|
||||
I::ViewReadingSmall => {
|
||||
@@ -1320,7 +1359,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
}
|
||||
I::ViewShowSmall => {
|
||||
p.seg((2.0, 4.0), (22.0, 4.0), K);
|
||||
p.rect(3.5, 4.0, 20.5, 16.0, 0.8, N, K);
|
||||
p.rect((3.5, 4.0, 20.5, 16.0), 0.8, N, K);
|
||||
p.seg((12.0, 16.0), (12.0, 19.0), K);
|
||||
p.line(&[(8.5, 22.0), (12.0, 19.0), (15.5, 22.0)], K);
|
||||
}
|
||||
@@ -1328,7 +1367,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
for (cx, cy, sx, sy) in [(3.0, 3.0, 1.0, 1.0), (21.0, 3.0, -1.0, 1.0), (3.0, 21.0, 1.0, -1.0), (21.0, 21.0, -1.0, -1.0)] {
|
||||
p.rline(&[(cx, cy + 5.0 * sy), (cx, cy), (cx + 5.0 * sx, cy)], 0.8, K);
|
||||
}
|
||||
p.rect(8.0, 8.5, 16.0, 15.5, 0.8, N, K);
|
||||
p.rect((8.0, 8.5, 16.0, 15.5), 0.8, N, K);
|
||||
}
|
||||
I::ZoomIn | I::ZoomOut => {
|
||||
p.magnifier((10.0, 10.0), 7.0, N, K);
|
||||
@@ -1337,5 +1376,7 @@ pub(super) fn draw(p: &Pen<'_>, icon: Icon) {
|
||||
p.seg((10.0, 7.0), (10.0, 13.0), K);
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, BLUE, Color32, DARK, FontId, GOLD, GREEN, Mesh, ORANGE, PI, PURPLE, Painter, PathShape, Pos2, RED, Rect, Shape, Stroke, StrokeKind,
|
||||
TextShape, pos2, vec2,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Drawing toolkit
|
||||
@@ -49,6 +52,8 @@ pub(super) fn t(c: C) -> C {
|
||||
}
|
||||
|
||||
pub(super) type P2 = (f32, f32);
|
||||
/// A box as `(x0, y0, x1, y1)`.
|
||||
pub(super) type Box4 = (f32, f32, f32, f32);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum Glyph {
|
||||
@@ -363,13 +368,13 @@ impl Pen<'_> {
|
||||
self.p.add(PathShape::convex_polygon(v, self.col(fill), self.stroke(stroke, w)));
|
||||
}
|
||||
|
||||
fn rect_w(&self, x0: f32, y0: f32, x1: f32, y1: f32, r: f32, fill: C, stroke: C, w: f32) {
|
||||
fn rect_w(&self, (x0, y0, x1, y1): Box4, r: f32, fill: C, stroke: C, w: f32) {
|
||||
let rect = Rect::from_min_max(self.pt((x0, y0)), self.pt((x1, y1)));
|
||||
self.p.rect(rect, r * self.k, self.col(fill), self.stroke(stroke, w), StrokeKind::Middle);
|
||||
}
|
||||
|
||||
pub(super) fn rect(&self, x0: f32, y0: f32, x1: f32, y1: f32, r: f32, fill: C, stroke: C) {
|
||||
self.rect_w(x0, y0, x1, y1, r, fill, stroke, 1.0);
|
||||
pub(super) fn rect(&self, b: Box4, r: f32, fill: C, stroke: C) {
|
||||
self.rect_w(b, r, fill, stroke, 1.0);
|
||||
}
|
||||
|
||||
fn circle_w(&self, c: P2, r: f32, fill: C, stroke: C, w: f32) {
|
||||
@@ -516,7 +521,7 @@ impl Pen<'_> {
|
||||
|
||||
/// Landscape slide frame.
|
||||
pub(super) fn frame(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||||
self.rect(x0, y0, x1, y1, 1.5, N, K);
|
||||
self.rect((x0, y0, x1, y1), 1.5, N, K);
|
||||
}
|
||||
|
||||
/// Document page with a folded top-right corner.
|
||||
@@ -530,7 +535,7 @@ impl Pen<'_> {
|
||||
pub(super) fn picture(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||||
let (w, h) = (x1 - x0, y1 - y0);
|
||||
let m = w.min(h);
|
||||
self.rect(x0, y0, x1, y1, 1.5, N, K);
|
||||
self.rect((x0, y0, x1, y1), 1.5, N, K);
|
||||
self.dot((x1 - w * 0.27, y0 + h * 0.3), m * 0.12, Y);
|
||||
let base = y1 - h * 0.14;
|
||||
self.poly(&[(x0 + w * 0.12, base), (x0 + w * 0.38, y0 + h * 0.36), (x0 + w * 0.64, base)], G, N);
|
||||
@@ -597,11 +602,11 @@ impl Pen<'_> {
|
||||
}
|
||||
|
||||
/// Table grid; `header` fills the first row.
|
||||
pub(super) fn table(&self, x0: f32, y0: f32, x1: f32, y1: f32, rows: usize, cols: usize, header: C) {
|
||||
pub(super) fn table(&self, (x0, y0, x1, y1): Box4, rows: usize, cols: usize, header: C) {
|
||||
let rh = (y1 - y0) / rows as f32;
|
||||
let cw = (x1 - x0) / cols as f32;
|
||||
if !matches!(header, C::None) {
|
||||
self.rect(x0, y0, x1, y0 + rh, 1.2, header, N);
|
||||
self.rect((x0, y0, x1, y0 + rh), 1.2, header, N);
|
||||
}
|
||||
for i in 1..rows {
|
||||
let y = y0 + rh * i as f32;
|
||||
@@ -611,7 +616,7 @@ impl Pen<'_> {
|
||||
let x = x0 + cw * j as f32;
|
||||
self.seg_w((x, y0), (x, y1), K, 0.8);
|
||||
}
|
||||
self.rect(x0, y0, x1, y1, 1.2, N, K);
|
||||
self.rect((x0, y0, x1, y1), 1.2, N, K);
|
||||
}
|
||||
|
||||
/// Bar chart in the box.
|
||||
@@ -620,13 +625,13 @@ impl Pen<'_> {
|
||||
let h = y1 - y0;
|
||||
for (i, (frac, c)) in [(0.5, B), (1.0, O), (0.72, G)].into_iter().enumerate() {
|
||||
let bx = x0 + w * i as f32;
|
||||
self.rect(bx + w * 0.16, y1 - h * frac, bx + w * 0.84, y1, 0.6, c, N);
|
||||
self.rect((bx + w * 0.16, y1 - h * frac, bx + w * 0.84, y1), 0.6, c, N);
|
||||
}
|
||||
self.seg((x0 - 0.5, y1 + 0.2), (x1 + 0.5, y1 + 0.2), K);
|
||||
}
|
||||
|
||||
/// A writing tool lying along `ang` degrees with its point at `tip`.
|
||||
pub(super) fn tool(&self, tip: P2, ang: f32, len: f32, hw: f32, cone: f32, body: C, lead: C, cap_len: f32, cap: C) {
|
||||
pub(super) fn tool(&self, (tip, ang): (P2, f32), (len, hw, cone): (f32, f32, f32), body: C, lead: C, (cap_len, cap): (f32, C)) {
|
||||
let m = |pts: &[P2]| tf(tip, ang, pts);
|
||||
self.poly(&m(&[(0.0, 0.0), (cone, -hw), (cone, hw)]), N, K);
|
||||
self.poly(&m(&[(0.0, 0.0), (cone * 0.42, -hw * 0.42), (cone * 0.42, hw * 0.42)]), lead, N);
|
||||
@@ -714,7 +719,7 @@ impl Pen<'_> {
|
||||
}
|
||||
|
||||
pub(super) fn monitor(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||||
self.rect(x0, y0, x1, y1, 1.5, N, K);
|
||||
self.rect((x0, y0, x1, y1), 1.5, N, K);
|
||||
let cx = (x0 + x1) * 0.5;
|
||||
self.seg((cx, y1), (cx, y1 + 3.2), K);
|
||||
self.seg((cx - 4.0, y1 + 3.6), (cx + 4.0, y1 + 3.6), K);
|
||||
@@ -749,7 +754,7 @@ impl Pen<'_> {
|
||||
sh.extend(arc_pts((cx, y0 - r * 0.7), r, r, 180.0, 360.0));
|
||||
sh.push((cx + r, y0));
|
||||
self.line(&sh, K);
|
||||
self.rect(x0, y0, x1, y1, 1.2, Y, K);
|
||||
self.rect((x0, y0, x1, y1), 1.2, Y, K);
|
||||
let ky = (y0 + y1) * 0.5;
|
||||
self.dot((cx, ky - 0.4), 1.1, D);
|
||||
self.seg_w((cx, ky), (cx, ky + 2.0), D, 0.9);
|
||||
|
||||
@@ -348,7 +348,7 @@ impl MediaHost {
|
||||
|
||||
/// Carry out a `media.*` request from the engine for presentation `doc_uid`.
|
||||
/// `slide` is the slide the request comes from: shape ids are only unique per slide.
|
||||
pub fn request(&mut self, doc_uid: u64, doc: &Presentation, slide: usize, action: &str, id: ShapeId, ms: Option<u64>, owner: Owner) {
|
||||
pub fn request(&mut self, doc_uid: u64, doc: &Presentation, slide: usize, action: &str, (id, ms): (ShapeId, Option<u64>), owner: Owner) {
|
||||
self.ensure_doc(doc_uid);
|
||||
let Some(clip) = find_clip(doc, id, Some(slide)) else { return };
|
||||
let t = ms.map(|v| v as f64 / 1000.0);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, AnimClass, AnimStart, Color32, CornerRadius, Icon, Rect, Sense, SlideApp, Stroke, Tokens, Ui, animtimeline, icons, json, paint_anim_tile,
|
||||
pos2, run, theme, vec2,
|
||||
};
|
||||
|
||||
/// Render the animation pane and route its controls through the engine's animation commands.
|
||||
pub(super) fn animation(app: &mut SlideApp, ui: &mut Ui) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Fill, SlideApp, Ui, Value, cref_param, json, run, scheme, theme, vec2, widgets};
|
||||
|
||||
pub(super) fn background(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let sc = scheme(app);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Align2, Color32, CornerRadius, Sense, SlideApp, Stroke, Tokens, Ui, Value, json, run, theme, vec2};
|
||||
|
||||
/// Review › Compare: the revised copy's slide-level changes; click to go there, Accept to take it,
|
||||
/// Reject to drop it.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Color32, CornerRadius, Rect, Sense, SlideApp, Stroke, Tokens, Ui, json, pos2, run, vec2};
|
||||
|
||||
/// Design Ideas: offline suggestions — the current slide in each theme and layout variations.
|
||||
/// Rendered idea thumbnails, kept until the deck, slide or pane width changes (rendering eight
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Fill, Icon, ShapeKind, SlideApp, Tokens, Ui, color_button, cref_param, json, run, scheme, theme, vec2, widgets};
|
||||
|
||||
/// Percent transparency (0 = opaque) of a solid fill, the same number `shape.fill`'s
|
||||
/// `transparency` parameter writes as an alpha transform. Anything else (no fill, a
|
||||
@@ -23,16 +23,7 @@ pub(super) fn fill_transparency_to_apply(shown_percent: f32, seed_percent: f32)
|
||||
Some(f64::from(shown) / 100.0)
|
||||
}
|
||||
|
||||
/// Render the shape-format pane with fill/line, effects, size/properties, text, and picture tabs.
|
||||
pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let Some(sh) = st.selected_shapes().first().map(|s| (*s).clone()) else {
|
||||
ui.label(egui::RichText::new("Select a shape to format it.").color(t.text_dim));
|
||||
return;
|
||||
};
|
||||
let x = deckcraft_engine::cmd::xfrm_of(&st.doc, &st.selection, &sh);
|
||||
let uid = st.uid;
|
||||
fn format_tabs(app: &mut SlideApp, ui: &mut Ui, sh: &deckcraft_model::Shape) {
|
||||
// Tabs as icons.
|
||||
ui.horizontal(|ui| {
|
||||
for (tab, icon, tip) in [
|
||||
@@ -52,100 +43,110 @@ pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
app.ui.format_tab = "picture".into();
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
match app.ui.format_tab.as_str() {
|
||||
"effects" => {
|
||||
ui.label(egui::RichText::new("Shadow").font(theme::bold(13.0)));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (l, v) in [
|
||||
("None", "none"),
|
||||
("Outer", "outer"),
|
||||
("Bottom", "bottom"),
|
||||
("Center", "center"),
|
||||
("Inner", "inner"),
|
||||
("Perspective", "perspective"),
|
||||
] {
|
||||
if ui.button(l).clicked() {
|
||||
run(app, "shape.effects", json!({"shadow": v}));
|
||||
}
|
||||
}
|
||||
});
|
||||
let fx = sh.effects.clone().unwrap_or_default();
|
||||
let shadow = fx.outer_shadow.as_ref().or(fx.inner_shadow.as_ref());
|
||||
if let Some(sw) = shadow {
|
||||
color_button(app, ui, "Color", None, |c| ("shape.effects".into(), json!({"shadowProps": {"color": c.as_ref().map(cref_param)}})));
|
||||
fx_slider(
|
||||
app,
|
||||
ui,
|
||||
("shadow", sh.id.0, "t"),
|
||||
"Transparency",
|
||||
(1.0 - sw.color.alpha()) * 100.0,
|
||||
0.0..=100.0,
|
||||
"%",
|
||||
"shadowProps",
|
||||
"transparency",
|
||||
0.01,
|
||||
);
|
||||
fx_slider(app, ui, ("shadow", sh.id.0, "b"), "Blur", sw.blur, 0.0..=100.0, " pt", "shadowProps", "blur", 1.0);
|
||||
fx_slider(app, ui, ("shadow", sh.id.0, "a"), "Angle", sw.dir, 0.0..=359.0, "°", "shadowProps", "dir", 1.0);
|
||||
fx_slider(app, ui, ("shadow", sh.id.0, "d"), "Distance", sw.dist, 0.0..=200.0, " pt", "shadowProps", "dist", 1.0);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.label(egui::RichText::new("Reflection").font(theme::bold(13.0)));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (l, v) in [("None", "none"), ("Tight", "tight"), ("Half", "half"), ("Full", "full")] {
|
||||
if ui.button(l).clicked() {
|
||||
run(app, "shape.effects", json!({"reflection": v}));
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Some(r) = &fx.reflection {
|
||||
fx_slider(
|
||||
app,
|
||||
ui,
|
||||
("refl", sh.id.0, "t"),
|
||||
"Transparency",
|
||||
(1.0 - r.start_alpha) * 100.0,
|
||||
0.0..=100.0,
|
||||
"%",
|
||||
"reflectionProps",
|
||||
"transparency",
|
||||
0.01,
|
||||
);
|
||||
fx_slider(app, ui, ("refl", sh.id.0, "s"), "Size", r.end_pos * 100.0, 0.0..=100.0, "%", "reflectionProps", "size", 0.01);
|
||||
fx_slider(app, ui, ("refl", sh.id.0, "d"), "Distance", r.dist, 0.0..=100.0, " pt", "reflectionProps", "dist", 1.0);
|
||||
fx_slider(app, ui, ("refl", sh.id.0, "b"), "Blur", r.blur, 0.0..=100.0, " pt", "reflectionProps", "blur", 1.0);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.label(egui::RichText::new("Glow").font(theme::bold(13.0)));
|
||||
let radius = fx.glow.as_ref().map_or(0.0, |g| g.radius);
|
||||
fx_slider(app, ui, ("glow", sh.id.0, "r"), "Size", radius, 0.0..=150.0, " pt", "glowProps", "radius", 1.0);
|
||||
if let Some(g) = &fx.glow {
|
||||
color_button(app, ui, "Color", None, |c| ("shape.effects".into(), json!({"glowProps": {"color": c.as_ref().map(cref_param)}})));
|
||||
fx_slider(
|
||||
app,
|
||||
ui,
|
||||
("glow", sh.id.0, "t"),
|
||||
"Transparency",
|
||||
(1.0 - g.color.alpha()) * 100.0,
|
||||
0.0..=100.0,
|
||||
"%",
|
||||
"glowProps",
|
||||
"transparency",
|
||||
0.01,
|
||||
);
|
||||
}
|
||||
ui.label(egui::RichText::new("Soft Edges").font(theme::bold(13.0)));
|
||||
let mut s = sh.effects.as_ref().and_then(|e| e.soft_edge).unwrap_or(0.0);
|
||||
if ui.add(egui::Slider::new(&mut s, 0.0..=50.0).text("size (pt)")).drag_stopped() {
|
||||
run(app, "shape.effects", json!({"softEdges": s}));
|
||||
}
|
||||
let raw3d = sh.effects.as_ref().and_then(|e| e.raw3d.clone());
|
||||
three_d_options(app, ui, sh.id.0, raw3d.as_deref());
|
||||
if ui.button("Reset Effects").clicked() {
|
||||
run(app, "shape.effects", json!({"reset": true}));
|
||||
}
|
||||
|
||||
fn effects_tab(app: &mut SlideApp, ui: &mut Ui, sh: &deckcraft_model::Shape) {
|
||||
ui.label(egui::RichText::new("Shadow").font(theme::bold(13.0)));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (l, v) in
|
||||
[("None", "none"), ("Outer", "outer"), ("Bottom", "bottom"), ("Center", "center"), ("Inner", "inner"), ("Perspective", "perspective")]
|
||||
{
|
||||
if ui.button(l).clicked() {
|
||||
run(app, "shape.effects", json!({"shadow": v}));
|
||||
}
|
||||
}
|
||||
});
|
||||
let fx = sh.effects.clone().unwrap_or_default();
|
||||
let shadow = fx.outer_shadow.as_ref().or(fx.inner_shadow.as_ref());
|
||||
if let Some(sw) = shadow {
|
||||
color_button(app, ui, "Color", None, |c| ("shape.effects".into(), json!({"shadowProps": {"color": c.as_ref().map(cref_param)}})));
|
||||
fx_slider(
|
||||
app,
|
||||
ui,
|
||||
("shadow", sh.id.0, "t"),
|
||||
"Transparency",
|
||||
(1.0 - sw.color.alpha()) * 100.0,
|
||||
0.0..=100.0,
|
||||
"%",
|
||||
"shadowProps",
|
||||
"transparency",
|
||||
0.01,
|
||||
);
|
||||
fx_slider(app, ui, ("shadow", sh.id.0, "b"), "Blur", sw.blur, 0.0..=100.0, " pt", "shadowProps", "blur", 1.0);
|
||||
fx_slider(app, ui, ("shadow", sh.id.0, "a"), "Angle", sw.dir, 0.0..=359.0, "°", "shadowProps", "dir", 1.0);
|
||||
fx_slider(app, ui, ("shadow", sh.id.0, "d"), "Distance", sw.dist, 0.0..=200.0, " pt", "shadowProps", "dist", 1.0);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.label(egui::RichText::new("Reflection").font(theme::bold(13.0)));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (l, v) in [("None", "none"), ("Tight", "tight"), ("Half", "half"), ("Full", "full")] {
|
||||
if ui.button(l).clicked() {
|
||||
run(app, "shape.effects", json!({"reflection": v}));
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Some(r) = &fx.reflection {
|
||||
fx_slider(
|
||||
app,
|
||||
ui,
|
||||
("refl", sh.id.0, "t"),
|
||||
"Transparency",
|
||||
(1.0 - r.start_alpha) * 100.0,
|
||||
0.0..=100.0,
|
||||
"%",
|
||||
"reflectionProps",
|
||||
"transparency",
|
||||
0.01,
|
||||
);
|
||||
fx_slider(app, ui, ("refl", sh.id.0, "s"), "Size", r.end_pos * 100.0, 0.0..=100.0, "%", "reflectionProps", "size", 0.01);
|
||||
fx_slider(app, ui, ("refl", sh.id.0, "d"), "Distance", r.dist, 0.0..=100.0, " pt", "reflectionProps", "dist", 1.0);
|
||||
fx_slider(app, ui, ("refl", sh.id.0, "b"), "Blur", r.blur, 0.0..=100.0, " pt", "reflectionProps", "blur", 1.0);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.label(egui::RichText::new("Glow").font(theme::bold(13.0)));
|
||||
let radius = fx.glow.as_ref().map_or(0.0, |g| g.radius);
|
||||
fx_slider(app, ui, ("glow", sh.id.0, "r"), "Size", radius, 0.0..=150.0, " pt", "glowProps", "radius", 1.0);
|
||||
if let Some(g) = &fx.glow {
|
||||
color_button(app, ui, "Color", None, |c| ("shape.effects".into(), json!({"glowProps": {"color": c.as_ref().map(cref_param)}})));
|
||||
fx_slider(
|
||||
app,
|
||||
ui,
|
||||
("glow", sh.id.0, "t"),
|
||||
"Transparency",
|
||||
(1.0 - g.color.alpha()) * 100.0,
|
||||
0.0..=100.0,
|
||||
"%",
|
||||
"glowProps",
|
||||
"transparency",
|
||||
0.01,
|
||||
);
|
||||
}
|
||||
ui.label(egui::RichText::new("Soft Edges").font(theme::bold(13.0)));
|
||||
let mut s = sh.effects.as_ref().and_then(|e| e.soft_edge).unwrap_or(0.0);
|
||||
if ui.add(egui::Slider::new(&mut s, 0.0..=50.0).text("size (pt)")).drag_stopped() {
|
||||
run(app, "shape.effects", json!({"softEdges": s}));
|
||||
}
|
||||
let raw3d = sh.effects.as_ref().and_then(|e| e.raw3d.clone());
|
||||
three_d_options(app, ui, sh.id.0, raw3d.as_deref());
|
||||
if ui.button("Reset Effects").clicked() {
|
||||
run(app, "shape.effects", json!({"reset": true}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the shape-format pane with fill/line, effects, size/properties, text, and picture tabs.
|
||||
pub(super) fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let Some(sh) = st.selected_shapes().first().map(|s| (*s).clone()) else {
|
||||
ui.label(egui::RichText::new("Select a shape to format it.").color(t.text_dim));
|
||||
return;
|
||||
};
|
||||
let x = deckcraft_engine::cmd::xfrm_of(&st.doc, &st.selection, &sh);
|
||||
let uid = st.uid;
|
||||
format_tabs(app, ui, &sh);
|
||||
ui.separator();
|
||||
match app.ui.format_tab.as_str() {
|
||||
"effects" => effects_tab(app, ui, &sh),
|
||||
"size" => {
|
||||
let lock_id = egui::Id::unique("fmt-lock-aspect");
|
||||
let mut lock: bool = ui.data_mut(|d| d.get_temp(lock_id).unwrap_or(false));
|
||||
|
||||
@@ -118,16 +118,7 @@ fn small_icon_button(ui: &mut Ui, icon: Icon, label: &str, enabled: bool) -> egu
|
||||
resp.on_hover_text(label)
|
||||
}
|
||||
|
||||
/// Render the Selection Pane and route visibility, order, rename, and selection actions through
|
||||
/// engine commands.
|
||||
pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some(st) = app.session.active() else { return };
|
||||
app.selpane.bind(st.uid);
|
||||
let rows = rows(st.shapes(), &app.selpane.collapsed);
|
||||
let selected: Vec<u32> = st.selection.shapes.iter().map(|i| i.0).collect();
|
||||
let has_sel = !selected.is_empty();
|
||||
|
||||
fn toolbar(app: &mut SlideApp, ui: &mut Ui, t: &Tokens, has_sel: bool) {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add(egui::Button::new("Show All").stroke(Stroke::new(1.0, t.border))).clicked() {
|
||||
run(app, "selection.showAll", json!({}));
|
||||
@@ -144,15 +135,49 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
// The list has the keyboard while one of its rows has focus.
|
||||
fn focus_lock(ui: &mut Ui, rows: &[Row]) -> Option<u32> {
|
||||
let focused_row = ui.memory(|m| m.focused()).and_then(|f| rows.iter().find(|r| row_id(r.id) == f).map(|r| r.id));
|
||||
if let Some(f) = focused_row {
|
||||
ui.memory_mut(|m| {
|
||||
m.set_focus_lock_filter(row_id(f), egui::EventFilter { tab: false, horizontal_arrows: true, vertical_arrows: true, escape: true })
|
||||
});
|
||||
}
|
||||
focused_row
|
||||
}
|
||||
|
||||
fn group_chevron(app: &mut SlideApp, ui: &mut Ui, row: &Row, painter: &egui::Painter, x0: f32, r: Rect, t: &Tokens) {
|
||||
let open = !app.selpane.collapsed.contains(&row.id);
|
||||
let cr = Rect::from_center_size(pos2(x0 + 6.0, r.center().y), vec2(14.0, 14.0));
|
||||
let cresp = ui.interact(cr, ui.scope_id().with(("fold", row.id)), Sense::click());
|
||||
let label = format!("{} {}", if open { "Collapse" } else { "Expand" }, row.name);
|
||||
cresp.widget_info(|| WidgetInfo::labeled(Role::Button, true, &label));
|
||||
icons::paint(painter, cr, if open { Icon::ChevronDown } else { Icon::ChevronRight }, t.text_dim, false);
|
||||
if cresp.clicked() {
|
||||
if open {
|
||||
app.selpane.collapsed.insert(row.id);
|
||||
} else {
|
||||
app.selpane.collapsed.remove(&row.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the Selection Pane and route visibility, order, rename, and selection actions through
|
||||
/// engine commands.
|
||||
pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some(st) = app.session.active() else { return };
|
||||
app.selpane.bind(st.uid);
|
||||
let rows = rows(st.shapes(), &app.selpane.collapsed);
|
||||
let selected: Vec<u32> = st.selection.shapes.iter().map(|i| i.0).collect();
|
||||
let has_sel = !selected.is_empty();
|
||||
|
||||
toolbar(app, ui, &t, has_sel);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// The list has the keyboard while one of its rows has focus.
|
||||
let focused_row = focus_lock(ui, &rows);
|
||||
let mut rects: Vec<Rect> = Vec::with_capacity(rows.len());
|
||||
let width = ui.available_width();
|
||||
for (k, row) in rows.iter().enumerate() {
|
||||
@@ -171,19 +196,7 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let x0 = r.min.x + 4.0 + INDENT * row.depth as f32;
|
||||
// Group chevron.
|
||||
if row.group {
|
||||
let open = !app.selpane.collapsed.contains(&row.id);
|
||||
let cr = Rect::from_center_size(pos2(x0 + 6.0, r.center().y), vec2(14.0, 14.0));
|
||||
let cresp = ui.interact(cr, ui.scope_id().with(("fold", row.id)), Sense::click());
|
||||
let label = format!("{} {}", if open { "Collapse" } else { "Expand" }, row.name);
|
||||
cresp.widget_info(|| WidgetInfo::labeled(Role::Button, true, &label));
|
||||
icons::paint(&painter, cr, if open { Icon::ChevronDown } else { Icon::ChevronRight }, t.text_dim, false);
|
||||
if cresp.clicked() {
|
||||
if open {
|
||||
app.selpane.collapsed.insert(row.id);
|
||||
} else {
|
||||
app.selpane.collapsed.remove(&row.id);
|
||||
}
|
||||
}
|
||||
group_chevron(app, ui, row, &painter, x0, r, &t);
|
||||
}
|
||||
let name_x = x0 + 16.0;
|
||||
let eye = Rect::from_center_size(pos2(r.max.x - 34.0, r.center().y), vec2(16.0, 16.0));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{SlideApp, Tokens, Ui, Value, json, run};
|
||||
|
||||
/// SmartArt Text Pane: one line per node, indented by level. Enter adds a line after, Tab and
|
||||
/// Shift+Tab demote and promote, Backspace in an empty line removes it.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{SlideApp, Tokens, Ui, json, run, theme};
|
||||
|
||||
/// Review › Thesaurus: look a word up; clicking a synonym puts it in place of the selected word
|
||||
/// when editing text, otherwise looks that synonym up in turn.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::transitions::option_label;
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, AnimClass, CornerRadius, Icon, Rect, Sense, SlideApp, Tokens, Ui, big, big_button, big_button_ex, dropdown, enabled, group, icons, json,
|
||||
pos2, rows, run, theme, vec2, widgets,
|
||||
};
|
||||
|
||||
// ---------- Animations ----------
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Icon, ShapeId, SlideApp, Ui, big, big_button, big_button_ex, enabled, group, json, menu_item, rows, run, small_button_ex, theme, widgets,
|
||||
};
|
||||
|
||||
pub fn arrange_menu(app: &mut SlideApp, ui: &mut Ui) {
|
||||
ui.label(egui::RichText::new("Order Objects").font(theme::bold(12.0)));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::arrange::arrange_group;
|
||||
use super::insert::chart_menu_types;
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Ui, big, big_button, group, json};
|
||||
|
||||
pub(super) fn chart_design(app: &mut SlideApp, ui: &mut Ui) {
|
||||
group(ui, |ui| {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, Color32, CornerRadius, Icon, Rect, Sense, SlideApp, Stroke, Tokens, Ui, big, big_button, enabled, group, json, pos2, run, scheme,
|
||||
small_button, theme, vec2, widgets,
|
||||
};
|
||||
|
||||
// ---------- Design ----------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Ui, big_button, big_button_ex, group, json, run};
|
||||
|
||||
// ---------- Draw ----------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Align2, Color32, CornerRadius, Rect, Sense, SlideApp, Stroke, Tokens, Ui, Value, json, pos2, run, scheme, theme, vec2, widgets};
|
||||
|
||||
/// A ColorRef as a command colour parameter.
|
||||
pub fn cref_param(c: &deckcraft_model::ColorRef) -> Value {
|
||||
@@ -60,8 +60,7 @@ pub(super) fn smartart_gallery(
|
||||
cmd: &str,
|
||||
extra: Value,
|
||||
current: &str,
|
||||
style: &str,
|
||||
colors: &str,
|
||||
(style, colors): (&str, &str),
|
||||
nodes: Option<&[deckcraft_model::DiagramNode]>,
|
||||
) {
|
||||
use deckcraft_engine::cmd::smartart::{LAYOUTS, category};
|
||||
@@ -86,7 +85,7 @@ pub(super) fn smartart_gallery(
|
||||
egui::Grid::new(("smartart_gallery", cat)).num_columns(4).spacing([6.0, 6.0]).show(ui, |ui| {
|
||||
for (i, (k, label)) in kinds.iter().enumerate() {
|
||||
let ctx = ui.ctx().clone();
|
||||
let thumb = app.session.active().and_then(|d| app.textures.smartart(&ctx, &d.doc, slide, k, style, colors, None, 192));
|
||||
let thumb = app.session.active().and_then(|d| app.textures.smartart(&ctx, &d.doc, slide, (k, style, colors), None, 192));
|
||||
let r = ui
|
||||
.vertical_centered(|ui| {
|
||||
ui.set_width(100.0);
|
||||
@@ -102,7 +101,7 @@ pub(super) fn smartart_gallery(
|
||||
})
|
||||
.inner;
|
||||
if r.hovered() {
|
||||
let preview = app.session.active().and_then(|d| app.textures.smartart(&ctx, &d.doc, slide, k, style, colors, nodes, 480));
|
||||
let preview = app.session.active().and_then(|d| app.textures.smartart(&ctx, &d.doc, slide, (k, style, colors), nodes, 480));
|
||||
if let Some(t) = preview {
|
||||
r.show_tooltip_ui(|ui| {
|
||||
ui.add(egui::Image::new(&t).fit_to_exact_size(egui::vec2(320.0, 200.0)));
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::arrange::arrange_menu;
|
||||
use super::galleries::{cref_param, effects_menu, outline_menu, quick_styles, shapes_gallery, smartart_gallery};
|
||||
use super::*;
|
||||
use super::{
|
||||
Color32, CornerRadius, Icon, Rect, Sense, SlideApp, Stroke, Tokens, Ui, Value, big, big_button, big_button_ex, color_split, dropdown, enabled,
|
||||
fmt_state, group, icon_toggle, json, pos2, rows, run, scheme, small, small_button, small_button_ex, small_button_named, toggle, vec2, widgets,
|
||||
};
|
||||
|
||||
// ---------- Home ----------
|
||||
|
||||
@@ -382,7 +385,7 @@ fn paragraph_group(app: &mut SlideApp, ui: &mut Ui, st: &Value) {
|
||||
}
|
||||
});
|
||||
let r = icon_toggle(ui, Icon::SmartArt, "Convert to SmartArt", enabled(app, "smartArt.fromText"), false);
|
||||
egui::Popup::menu(&r).show(|ui| smartart_gallery(app, ui, "smartArt.fromText", json!({}), "", "", "", None));
|
||||
egui::Popup::menu(&r).show(|ui| smartart_gallery(app, ui, "smartArt.fromText", json!({}), "", ("", ""), None));
|
||||
if ui.add_enabled(en, egui::Button::new("Paragraph…").min_size(vec2(88.0, 22.0))).on_hover_text("Paragraph").clicked() {
|
||||
run(app, "format.paragraphDialog", json!({}));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::galleries::{Fl, kurbo_flatten, shapes_gallery};
|
||||
use super::home::layout_menu;
|
||||
use super::view::zoom_menu;
|
||||
use super::*;
|
||||
use super::{
|
||||
Color32, CornerRadius, Icon, Rect, Sense, SlideApp, Stroke, Tokens, Ui, big, big_button, big_button_ex, enabled, group, json, menu_item, pos2,
|
||||
run, tip, vec2,
|
||||
};
|
||||
|
||||
/// Photo Album is a split button: the main click starts a new album, the arrow edits one.
|
||||
fn photo_album_button(app: &mut SlideApp, ui: &mut Ui) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Target, Ui, big, big_button, group, json, rows, run};
|
||||
|
||||
/// Preserve, and whether the open master or layout has a title or any footer placeholder.
|
||||
fn master_checks(app: &SlideApp) -> (bool, bool, bool) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::arrange::{arrange_group, size_group};
|
||||
use super::galleries::{cref_param, shapes_gallery};
|
||||
use super::*;
|
||||
use super::{
|
||||
Color32, CornerRadius, Icon, Rect, SlideApp, Stroke, Ui, Value, big, big_button, color_split, enabled, group, json, pos2, rows, run, scheme,
|
||||
small, small_button_ex, theme, vec2, widgets,
|
||||
};
|
||||
|
||||
/// Render the Picture Format tab's corrections, colour, artistic effects, transparency, styles,
|
||||
/// border, and picture actions.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Icon, ShapeKind, SlideApp, Ui, big, big_button, group, json, rows, run};
|
||||
|
||||
pub(super) fn playback(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let m = app
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Ui, Value, big, big_button, big_button_ex, enabled, group, json, rows, run, small_button, tip};
|
||||
|
||||
fn comment_nav(app: &mut SlideApp, ui: &mut Ui, forward: bool) {
|
||||
let (icon, label, id) = if forward { (Icon::NextComment, "Next", "comment.next") } else { (Icon::PrevComment, "Previous", "comment.prev") };
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::arrange::{arrange_group, size_group};
|
||||
use super::galleries::{cref_param, effects_menu, outline_menu, quick_styles, shapes_gallery};
|
||||
use super::*;
|
||||
use super::{
|
||||
Color32, CornerRadius, Icon, Rect, Sense, SlideApp, Stroke, Tokens, Ui, big, big_button, enabled, group, json, pos2, rows, run, scheme,
|
||||
small_button, small_button_ex, theme, vec2, widgets,
|
||||
};
|
||||
|
||||
// ---------- contextual tabs ----------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Ui, big, big_button, big_button_ex, group, json, rows, run};
|
||||
|
||||
// ---------- Slide Show / Record / Review / View ----------
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::arrange::arrange_group;
|
||||
use super::galleries::smartart_gallery;
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Ui, Value, big_button, group, json, rows, run, small_button, small_button_ex, widgets};
|
||||
|
||||
/// SmartArt Design: Create Graphic (Text Pane, Add Shape, Promote, Demote, Remove) and Layouts.
|
||||
pub(super) fn smartart_design(app: &mut SlideApp, ui: &mut Ui) {
|
||||
@@ -53,7 +53,7 @@ pub(super) fn smartart_design(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let outline = sel.as_ref().map(|(_, d)| d.nodes.clone());
|
||||
if let Some(arrow) = arrow {
|
||||
widgets::split_menu(&main, &arrow, |ui| {
|
||||
smartart_gallery(app, ui, "smartArt.layout", json!({"id": gid}), &kind, &style, &colors, outline.as_deref());
|
||||
smartart_gallery(app, ui, "smartArt.layout", json!({"id": gid}), &kind, (&style, &colors), outline.as_deref());
|
||||
});
|
||||
}
|
||||
let (main, arrow) = small_button_ex(ui, Icon::Layout, "Convert", gid.is_some(), false, true);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::arrange::arrange_group;
|
||||
use super::galleries::cref_param;
|
||||
use super::*;
|
||||
use super::{
|
||||
Color32, CornerRadius, Icon, Rect, Sense, ShapeKind, SlideApp, Stroke, Ui, Value, big, big_button, big_button_ex, color_split, group, json,
|
||||
menu_item, pos2, rows, run, scheme, small, theme, toggle, vec2, widgets,
|
||||
};
|
||||
|
||||
pub(super) fn table_design(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let tbl = app
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Align2, Icon, Rect, Sense, SlideApp, Tokens, Ui, icon_toggle, json, pos2, theme, vec2};
|
||||
|
||||
pub const TITLE_H: f32 = 38.0;
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::animations::marked_options;
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, Color32, CornerRadius, Icon, Rect, Sense, SlideApp, Stroke, Tokens, Ui, Value, big, big_button, group, json, pos2, rows, run, theme,
|
||||
vec2, widgets,
|
||||
};
|
||||
|
||||
// ---------- Transitions ----------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Icon, SlideApp, Target, Ui, big_button, big_button_ex, group, json, rows, run, theme};
|
||||
|
||||
pub(super) fn view(app: &mut SlideApp, ui: &mut Ui) {
|
||||
use crate::ViewMode;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::presenter::NOTES_STEP;
|
||||
use super::zoom::{plain, toggle_blank};
|
||||
use super::*;
|
||||
use super::{Color32, Presentation, Show, ShowAction};
|
||||
|
||||
/// Read the current viewport's show keys before its widgets can consume them. Both the
|
||||
/// audience window and Presenter View use this path, including animation steps and end-of-show.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::render::fit;
|
||||
use super::*;
|
||||
use super::{Click, HitShape, Hyperlink, Presentation, Rect, ShapeId, Show, SlideApp, Ui, pos2, vec2};
|
||||
|
||||
/// The media-host voice for slide transition sounds (no shape has this id).
|
||||
pub(crate) const TRANSITION_SOUND: ShapeId = ShapeId(u32::MAX);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::keys::keyboard;
|
||||
use super::render::{fit, next_rect};
|
||||
use super::zoom::{Zoom, grid_indices, paint_grid, see_all_hit, slide_aspect, toggle_blank, zoom_frame};
|
||||
use super::*;
|
||||
use super::{Align2, Color32, CornerRadius, Presentation, Rect, RenderOpts, Sense, Show, Stroke, Ui, pos2, theme, vec2};
|
||||
|
||||
pub(super) const NOTES_DEFAULT: f32 = 18.0;
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::zoom::GRID_CAP;
|
||||
use super::*;
|
||||
use super::{
|
||||
BackKey, Color32, Key, Layer, Mesh, Prefetch, Presentation, Rect, RenderOpts, ShapeId, Show, ShowState, StepStamp, TextureHandle, Timeline,
|
||||
TriggerPlay, pos2, state_for, vec2,
|
||||
};
|
||||
|
||||
pub(super) fn render_threads() -> u16 {
|
||||
if cfg!(target_arch = "wasm32") { 0 } else { std::thread::available_parallelism().map(|n| n.get().min(8) as u16).unwrap_or(0) }
|
||||
|
||||
+431
-309
@@ -8,7 +8,49 @@ use super::zoom::{
|
||||
Zoom, grid_indices, norm_on_slide, paint_grid, see_all_hit, slide_aspect, slide_box, slide_to_screen, toggle_blank, tool_bar_rect, unit_uv,
|
||||
wheel_zoom_lines,
|
||||
};
|
||||
use super::*;
|
||||
use super::{
|
||||
Align2, BackKey, Click, Color32, CornerRadius, Key, LinkJump, Pos2, Rect, RenderOpts, Sense, ShapeId, ShapeState, Show, ShowAction, SlideApp,
|
||||
Source, Stroke, TextureHandle, Ui, json, pos2, show_fills_screen, state_for, theme, vec2, viewport_visible,
|
||||
};
|
||||
use deckcraft_anim::{StepStamp, TriggerPlay};
|
||||
use deckcraft_model::Presentation;
|
||||
|
||||
/// Per-frame values the show's draw steps share.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Fr<'a> {
|
||||
ctx: &'a egui::Context,
|
||||
doc: &'a std::sync::Arc<Presentation>,
|
||||
doc_uid: u64,
|
||||
now: f64,
|
||||
full: Rect,
|
||||
srect: Rect,
|
||||
clicked: bool,
|
||||
secondary: bool,
|
||||
pointer: Option<Pos2>,
|
||||
}
|
||||
|
||||
/// Whether the show goes on, and the steps input asked for.
|
||||
struct Out {
|
||||
keep: bool,
|
||||
actions: Vec<ShowAction>,
|
||||
}
|
||||
|
||||
/// What is drawn on the current slide this frame.
|
||||
struct Sl {
|
||||
idx: usize,
|
||||
step: usize,
|
||||
animating: bool,
|
||||
t: f64,
|
||||
plays: Vec<TriggerPlay>,
|
||||
stamps: Vec<StepStamp>,
|
||||
media: Vec<(ShapeId, deckcraft_geom::Xfrm, deckcraft_model::MediaClip)>,
|
||||
hidden: Vec<ShapeId>,
|
||||
times: Vec<(ShapeId, f64)>,
|
||||
gifs: Vec<crate::media::Gif>,
|
||||
key: Key,
|
||||
back_key: BackKey,
|
||||
size: (u32, u32),
|
||||
}
|
||||
|
||||
/// Draw and advance the active show, handling hidden frames, timing, input, transitions, and
|
||||
/// presenter/reading presentation.
|
||||
@@ -24,20 +66,51 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
// A forced hidden paint (for example a pending screenshot) draws the current frame;
|
||||
// the show clocks are advanced by logic, not by this draw, so it must not advance them a
|
||||
// second time. Keep drawing the last frame rather than returning a blank.
|
||||
if show.started {
|
||||
let full = ui.max_rect();
|
||||
let avail = if show.reading { Rect::from_min_max(full.min, pos2(full.max.x, full.max.y - 34.0)) } else { full };
|
||||
let srect = fit(avail, &doc);
|
||||
let painter = ui.painter_at(full);
|
||||
painter.rect_filled(full, CornerRadius::ZERO, Color32::BLACK);
|
||||
if let Some((_, tex)) = &show.tex {
|
||||
painter.image(tex.id(), srect, unit_uv(), Color32::WHITE);
|
||||
}
|
||||
}
|
||||
paint_hidden(ui, &doc, &show);
|
||||
app.show = Some(show);
|
||||
return;
|
||||
}
|
||||
show.observe_visible_frame(now, web_time::Instant::now());
|
||||
begin_show(&ctx, &mut show, &doc, now);
|
||||
let mut out = Out { keep: keyboard(&ctx, &mut show, &doc, now, false), actions: vec![] };
|
||||
let (clicked, secondary, moved) = ctx.input(|i| (i.pointer.primary_clicked(), i.pointer.secondary_clicked(), i.pointer.delta().length() > 0.0));
|
||||
if moved {
|
||||
show.last_move = now;
|
||||
}
|
||||
let full = ui.max_rect();
|
||||
let avail = if show.reading { Rect::from_min_max(full.min, pos2(full.max.x, full.max.y - 34.0)) } else { full };
|
||||
let srect = fit(avail, &doc);
|
||||
let pointer = ctx.input(|i| i.pointer.interact_pos());
|
||||
show.expire_goto(now);
|
||||
// The clock of the triggered animations runs before anything reacts to the pointer.
|
||||
show.tick_triggers(&doc, now);
|
||||
let f = Fr { ctx: &ctx, doc: &doc, doc_uid, now, full, srect, clicked, secondary, pointer };
|
||||
let pointing = handle_clicks(app, ui, &mut show, &f, &mut out);
|
||||
handle_wheel_and_swipe(&mut show, &f, &mut out);
|
||||
advance(app, &mut show, &f, &mut out);
|
||||
let painter = ui.painter_at(full);
|
||||
painter.rect_filled(full, CornerRadius::ZERO, Color32::BLACK);
|
||||
draw_slide(app, ui, &mut show, &f, &painter);
|
||||
draw_pen_and_hints(ui, &mut show, &f, &painter, pointing);
|
||||
draw_bars(ui, &mut show, &f, &painter, &mut out);
|
||||
draw_extras(&mut show, &f, &painter, &mut out);
|
||||
finish(app, show, &f, out.keep);
|
||||
}
|
||||
|
||||
fn paint_hidden(ui: &Ui, doc: &Presentation, show: &Show) {
|
||||
if show.started {
|
||||
let full = ui.max_rect();
|
||||
let avail = if show.reading { Rect::from_min_max(full.min, pos2(full.max.x, full.max.y - 34.0)) } else { full };
|
||||
let srect = fit(avail, doc);
|
||||
let painter = ui.painter_at(full);
|
||||
painter.rect_filled(full, CornerRadius::ZERO, Color32::BLACK);
|
||||
if let Some((_, tex)) = &show.tex {
|
||||
painter.image(tex.id(), srect, unit_uv(), Color32::WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_show(ctx: &egui::Context, show: &mut Show, doc: &Presentation, now: f64) {
|
||||
if !show.started {
|
||||
show.started = true;
|
||||
show.slide_start = now;
|
||||
@@ -51,21 +124,12 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
if show_fills_screen(show.reading, &doc.show.show_type) {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(true));
|
||||
}
|
||||
show.after_enter(&doc, now);
|
||||
show.after_enter(doc, now);
|
||||
}
|
||||
let mut keep = keyboard(&ctx, &mut show, &doc, now, false);
|
||||
let (clicked, secondary, moved) = ctx.input(|i| (i.pointer.primary_clicked(), i.pointer.secondary_clicked(), i.pointer.delta().length() > 0.0));
|
||||
if moved {
|
||||
show.last_move = now;
|
||||
}
|
||||
let mut actions: Vec<ShowAction> = vec![];
|
||||
let full = ui.max_rect();
|
||||
let avail = if show.reading { Rect::from_min_max(full.min, pos2(full.max.x, full.max.y - 34.0)) } else { full };
|
||||
let srect = fit(avail, &doc);
|
||||
let pointer = ctx.input(|i| i.pointer.interact_pos());
|
||||
show.expire_goto(now);
|
||||
// The clock of the triggered animations runs before anything reacts to the pointer.
|
||||
show.tick_triggers(&doc, now);
|
||||
}
|
||||
|
||||
fn handle_clicks(app: &mut SlideApp, ui: &mut Ui, show: &mut Show, f: &Fr, out: &mut Out) -> bool {
|
||||
let Fr { ctx, doc, doc_uid, now, full, srect, clicked, secondary, pointer } = *f;
|
||||
let on_slide = !show.grid
|
||||
&& !show.pen
|
||||
&& !show.laser
|
||||
@@ -73,31 +137,28 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
&& show.blank.is_none()
|
||||
&& show.zoom.is_none()
|
||||
&& !show.zoom_arm
|
||||
&& !show.reading_bar_hit(&ctx)
|
||||
&& !pointer_on_media_bar(&ctx, ui, &doc, &show, now);
|
||||
&& !show.reading_bar_hit(ctx)
|
||||
&& !pointer_on_media_bar(ctx, ui, doc, show, now);
|
||||
// The button is drawn only while the pointer is awake; a hidden button takes no click.
|
||||
let gif_click = if clicked && on_slide && doc.show.show_media_controls && show.pointer_awake(now) {
|
||||
gif_hit(&ctx, ui, &show, doc_uid, &doc, app)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let media_click = if clicked && on_slide && gif_click.is_none() { media_hit(&ctx, ui, &show, &doc) } else { None };
|
||||
let under = if on_slide && gif_click.is_none() && media_click.is_none() { shapes_under_pointer(&ctx, ui, &mut show, &doc, now) } else { None };
|
||||
let gif_click =
|
||||
if clicked && on_slide && doc.show.show_media_controls && show.pointer_awake(now) { gif_hit(ctx, ui, show, doc_uid, doc, app) } else { None };
|
||||
let media_click = if clicked && on_slide && gif_click.is_none() { media_hit(ctx, ui, show, doc) } else { None };
|
||||
let under = if on_slide && gif_click.is_none() && media_click.is_none() { shapes_under_pointer(ctx, ui, show, doc, now) } else { None };
|
||||
let pointing = under.as_ref().is_some_and(|u| u.pick.is_some());
|
||||
if let Some(id) = gif_click {
|
||||
let _ = app.run("media.gifPlay", json!({"id": id}));
|
||||
} else if let Some((id, clip)) = media_click {
|
||||
app.media.toggle(doc_uid, &doc, id, &clip, crate::media::Owner::Show);
|
||||
app.media.toggle(doc_uid, doc, id, &clip, crate::media::Owner::Show);
|
||||
} else if clicked && let Some(Pick::Text(l)) = under.as_ref().and_then(|u| u.pick.clone()) {
|
||||
let last = show.last_viewed;
|
||||
match show.state.follow_link(&doc, &l, last) {
|
||||
LinkJump::Show(a) => actions.push(a),
|
||||
match show.state.follow_link(doc, &l, last) {
|
||||
LinkJump::Show(a) => out.actions.push(a),
|
||||
LinkJump::Open(url) => ctx.open_url(egui::OpenUrl::new_tab(url)),
|
||||
LinkJump::Exit => keep = false,
|
||||
LinkJump::Exit => out.keep = false,
|
||||
LinkJump::None => {}
|
||||
}
|
||||
} else if show.grid && clicked {
|
||||
let aspect = slide_aspect(&doc);
|
||||
let aspect = slide_aspect(doc);
|
||||
if let Some(p) = pointer
|
||||
&& let Some(n) = see_all_hit(full, grid_indices(doc.slides.len()).len(), aspect, p)
|
||||
&& let Some(&i) = grid_indices(doc.slides.len()).get(n)
|
||||
@@ -106,36 +167,41 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
show.blank = None;
|
||||
show.zoom = None;
|
||||
show.zoom_arm = false;
|
||||
actions.push(show.state.goto(&doc, i));
|
||||
out.actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
} else if clicked && show.zoom_arm && !show.reading_bar_hit(&ctx) {
|
||||
} else if clicked && show.zoom_arm && !show.reading_bar_hit(ctx) {
|
||||
if let Some(p) = pointer
|
||||
&& let Some((nx, ny)) = norm_on_slide(srect, unit_uv(), p)
|
||||
{
|
||||
show.zoom = Some(Zoom::at(nx, ny));
|
||||
show.zoom_arm = false;
|
||||
}
|
||||
} else if clicked && show.zoom.is_some() && !show.pen && !show.reading_bar_hit(&ctx) {
|
||||
} else if clicked && show.zoom.is_some() && !show.pen && !show.reading_bar_hit(ctx) {
|
||||
show.dismiss_zoom();
|
||||
} else if clicked && !show.pen && !show.reading_bar_hit(&ctx) && !pointer_on_media_bar(&ctx, ui, &doc, &show, now) {
|
||||
} else if clicked && !show.pen && !show.reading_bar_hit(ctx) && !pointer_on_media_bar(ctx, ui, doc, show, now) {
|
||||
if show.ended {
|
||||
keep = false;
|
||||
out.keep = false;
|
||||
} else if show.blank.is_some() {
|
||||
show.blank = None;
|
||||
} else {
|
||||
// A shape with an action or a triggered animation, or the background.
|
||||
let none = Under::default();
|
||||
match show.click_slide(&doc, under.as_ref().unwrap_or(&none), now) {
|
||||
Click::Show(a) => actions.push(a),
|
||||
match show.click_slide(doc, under.as_ref().unwrap_or(&none), now) {
|
||||
Click::Show(a) => out.actions.push(a),
|
||||
Click::Open(url) => ctx.open_url(egui::OpenUrl::new_tab(url)),
|
||||
Click::Exit => keep = false,
|
||||
Click::Exit => out.keep = false,
|
||||
Click::Animated | Click::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if secondary && !show.ended && !show.grid && show.zoom.is_none() && !show.zoom_arm {
|
||||
actions.push(show.state.prev(&doc));
|
||||
out.actions.push(show.state.prev(doc));
|
||||
}
|
||||
pointing
|
||||
}
|
||||
|
||||
fn handle_wheel_and_swipe(show: &mut Show, f: &Fr, out: &mut Out) {
|
||||
let Fr { ctx, doc, now, srect, pointer, .. } = *f;
|
||||
// Ctrl+wheel (Cmd+wheel on a Mac) zooms around the pointer, as in PowerPoint (#1286). egui
|
||||
// keeps it out of `smooth_scroll_delta`, so it does not also turn the page below.
|
||||
let line_points = ctx.options(|o| o.input_options.line_scroll_speed);
|
||||
@@ -156,45 +222,303 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
show.last_swipe = now;
|
||||
if next {
|
||||
if show.ended {
|
||||
keep = false;
|
||||
out.keep = false;
|
||||
} else {
|
||||
actions.push(show.state.next(&doc));
|
||||
out.actions.push(show.state.next(doc));
|
||||
}
|
||||
} else {
|
||||
show.ended = false;
|
||||
actions.push(show.state.prev(&doc));
|
||||
out.actions.push(show.state.prev(doc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn advance(app: &mut SlideApp, show: &mut Show, f: &Fr, out: &mut Out) {
|
||||
let Fr { doc, doc_uid, now, .. } = *f;
|
||||
// Apply input first: a step started by this click must restart the clock before it is read.
|
||||
for a in actions.drain(..) {
|
||||
if !show.apply(a, &doc, now) {
|
||||
keep = false;
|
||||
for a in out.actions.drain(..) {
|
||||
if !show.apply(a, doc, now) {
|
||||
out.keep = false;
|
||||
}
|
||||
}
|
||||
// Step clock, shared with the hidden logic tick.
|
||||
if !show.queue_clock_actions(&doc, now, &mut actions) {
|
||||
keep = false;
|
||||
if !show.queue_clock_actions(doc, now, &mut out.actions) {
|
||||
out.keep = false;
|
||||
}
|
||||
for a in actions {
|
||||
if !show.apply(a, &doc, now) {
|
||||
keep = false;
|
||||
for a in std::mem::take(&mut out.actions) {
|
||||
if !show.apply(a, doc, now) {
|
||||
out.keep = false;
|
||||
}
|
||||
}
|
||||
if show.state.playing {
|
||||
let step = show.state.step;
|
||||
show.cue_media(&doc, step, now - show.step_start);
|
||||
show.cue_media(doc, step, now - show.step_start);
|
||||
}
|
||||
show_media(app, &mut show, doc_uid, &doc);
|
||||
run_media_cues(app, &mut show, doc_uid, &doc);
|
||||
// Draw.
|
||||
let painter = ui.painter_at(full);
|
||||
painter.rect_filled(full, CornerRadius::ZERO, Color32::BLACK);
|
||||
show_media(app, show, doc_uid, doc);
|
||||
run_media_cues(app, show, doc_uid, doc);
|
||||
}
|
||||
|
||||
fn begin_slide(app: &mut SlideApp, show: &Show, f: &Fr, size: (u32, u32)) -> Sl {
|
||||
let Fr { ctx, doc, doc_uid, now, .. } = *f;
|
||||
let idx = show.state.slide;
|
||||
let step = show.state.step;
|
||||
// A main step or a triggered animation is running: the picture changes every frame.
|
||||
let animating = show.state.playing || show.state.trigger_playing();
|
||||
let t = if show.state.playing { now - show.step_start } else { 0.0 };
|
||||
let plays = show.state.trigger_plays();
|
||||
let stamps = show.state.trigger_stamps();
|
||||
let media = crate::media::slide_media(doc, idx);
|
||||
let hidden: Vec<ShapeId> = media
|
||||
.iter()
|
||||
.filter(|(id, _, clip)| clip.hide_while_not_playing && !app.media.status(*id).is_some_and(|s| s.state != deckcraft_media::PlayState::Ended))
|
||||
.map(|m| m.0)
|
||||
.collect();
|
||||
let gifs = app.media.gifs(doc_uid, doc, idx, now, &app.session.gif_paused);
|
||||
let times: Vec<_> = gifs.iter().map(|g| (g.id, g.t)).collect();
|
||||
crate::media::gif_repaint(ctx, &gifs);
|
||||
let gif = crate::media::gif_key(&gifs);
|
||||
let done: Vec<(ShapeId, usize)> = plays.iter().map(|p| (p.trigger, p.step)).collect();
|
||||
let key = Key { slide: idx, step, size, doc: std::sync::Arc::as_ptr(doc) as usize, hidden: hidden.clone(), plays: done, gif };
|
||||
let back_key = BackKey { slide: idx, size, doc: key.doc };
|
||||
Sl { idx, step, animating, t, plays, stamps, media, hidden, times, gifs, key, back_key, size }
|
||||
}
|
||||
|
||||
fn frame_texture(show: &mut Show, f: &Fr, sl: &Sl) -> Option<TextureHandle> {
|
||||
let Fr { ctx, doc, .. } = *f;
|
||||
let Sl { idx, step, animating, size, .. } = *sl;
|
||||
if !animating && show.tex.as_ref().is_some_and(|(k, _)| *k == sl.key) {
|
||||
show.tex.as_ref().map(|(_, t)| t.clone())
|
||||
} else {
|
||||
// The frame rendered ahead of time, when it is this one (waiting for it beats starting over).
|
||||
let ready = match show.prefetch.take() {
|
||||
Some(p) if p.key == sl.key && !animating => p.rx.recv().ok(),
|
||||
_ => None,
|
||||
};
|
||||
let ci = match ready {
|
||||
Some(ci) => ci,
|
||||
None => {
|
||||
let tl = show.timeline(doc).clone();
|
||||
// Animating: the backdrop doesn't change, only the shapes over it are drawn.
|
||||
let back = animating.then(|| show.backdrop(doc, sl.back_key));
|
||||
render_frame(doc, idx, tl, step, sl.t, size, sl.hidden.clone(), &sl.times, render_threads(), &sl.plays, &sl.stamps, back.as_deref())
|
||||
}
|
||||
};
|
||||
let tex = match show.tex.take() {
|
||||
Some((_, mut h)) => {
|
||||
crate::textures::replace(ctx, &mut h, ci);
|
||||
h
|
||||
}
|
||||
None => crate::textures::upload(ctx, "show", ci),
|
||||
};
|
||||
show.tex = Some((sl.key.clone(), tex.clone()));
|
||||
Some(tex)
|
||||
}
|
||||
}
|
||||
|
||||
fn prefetch_next(app: &mut SlideApp, show: &mut Show, f: &Fr, sl: &Sl) {
|
||||
let Fr { doc, doc_uid, now, .. } = *f;
|
||||
let Sl { animating, size, .. } = *sl;
|
||||
// While this slide rests, render the next one's first frame (not one with GIFs, whose
|
||||
// frame depends on when it is entered).
|
||||
// A slide with animations gets its backdrop ready while it rests, so they start smoothly.
|
||||
if !animating && !show.timeline(doc).is_empty() {
|
||||
show.prefetch_backdrop(doc, sl.back_key);
|
||||
}
|
||||
if !animating && show.trans.is_none() && !show.preview_only {
|
||||
let next = show.state.peek_next(doc);
|
||||
let wanted = next.filter(|n| app.media.gifs(doc_uid, doc, *n, now, &app.session.gif_paused).is_empty());
|
||||
if let Some(n) = wanted
|
||||
&& show.prefetch.as_ref().is_none_or(|p| p.key.slide != n || p.key.size != size || p.key.doc != std::sync::Arc::as_ptr(doc) as usize)
|
||||
{
|
||||
show.prefetch = start_prefetch(doc, n, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_transition(show: &mut Show, f: &Fr, sl: &Sl, painter: &egui::Painter, tex: &Option<TextureHandle>) -> bool {
|
||||
let Fr { ctx, doc, now, srect, .. } = *f;
|
||||
let Sl { idx, size, .. } = *sl;
|
||||
let mut drew = false;
|
||||
let morphing = show.trans.as_ref().is_some_and(|tr| tr.kind == "morph" && tr.from.is_some() && now - tr.start < tr.dur);
|
||||
let tl = if morphing { Some(show.timeline(doc).clone()) } else { None };
|
||||
let morph = match (&show.trans, tl) {
|
||||
(Some(tr), Some(tl)) => {
|
||||
let p = ((now - tr.start) / tr.dur.max(0.01)).clamp(0.0, 1.0);
|
||||
let hidden_b = |id: ShapeId| {
|
||||
let st = state_for(&tl, id, 0, 0.0, &[], &[]);
|
||||
!st.visible || st.opacity <= 0.0 || sl.hidden.contains(&id)
|
||||
};
|
||||
tr.from
|
||||
.and_then(|i| doc.slides.get(i).map(|a| (i, a)))
|
||||
.zip(doc.slides.get(idx))
|
||||
.map(|((i, a), b)| (i, deckcraft_anim::morph_frame(doc, a, b, p, &hidden_b)))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let (Some((from, m)), Some(tr)) = (morph, show.trans.as_mut()) {
|
||||
let f = |id: ShapeId| ShapeState { opacity: m.opacity_of(id), ..Default::default() };
|
||||
let threads = if cfg!(target_arch = "wasm32") { 0 } else { std::thread::available_parallelism().map(|n| n.get().min(8) as u16).unwrap_or(0) };
|
||||
let opts =
|
||||
RenderOpts { scale: size.0 as f64 / doc.slide_size.width.max(1.0), size: Some(size), state: Some(&f), threads, ..Default::default() };
|
||||
let text: Vec<deckcraft_render::TextMorph> =
|
||||
m.text.iter().map(|x| deckcraft_render::TextMorph { old: x.old, new: x.new, from: x.from, to: x.to, chars: x.chars, t: m.mix }).collect();
|
||||
let paths: Vec<deckcraft_render::PathMorph> = m
|
||||
.paths
|
||||
.iter()
|
||||
.map(|x| deckcraft_render::PathMorph { id: x.id, from: x.from.clone(), from_box: x.from_box, to_box: x.to_box, t: m.mix })
|
||||
.collect();
|
||||
let img = deckcraft_render::render_blend(doc, (from, idx), m.mix, &m.shapes, &text, &paths, &opts);
|
||||
let ci = crate::textures::to_color_image(&img, false);
|
||||
let frame = match tr.morph.take() {
|
||||
Some(mut h) => {
|
||||
crate::textures::replace(ctx, &mut h, ci);
|
||||
h
|
||||
}
|
||||
None => crate::textures::upload(ctx, "morph", ci),
|
||||
};
|
||||
painter.image(frame.id(), srect, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
tr.morph = Some(frame);
|
||||
drew = true;
|
||||
ctx.request_repaint();
|
||||
} else if let (Some(tr), Some(new)) = (show.trans.as_mut(), &tex) {
|
||||
let p = ((now - tr.start) / tr.dur.max(0.01)).clamp(0.0, 1.0);
|
||||
if p < 1.0 {
|
||||
// Slides split into background and content are rendered once per transition;
|
||||
// every frame only composites cached textures.
|
||||
if tr.parts.is_none() && deckcraft_anim::uses_slide_parts(&tr.kind, &tr.option) {
|
||||
tr.parts = Some(part_textures(ctx, doc, tr.from, idx, size));
|
||||
}
|
||||
let aspect = doc.slide_size.width / doc.slide_size.height.max(1.0);
|
||||
let part = |k: usize| tr.parts.as_ref().and_then(|p| p.get(k).cloned().flatten());
|
||||
let parts = [part(0), part(1), part(2), part(3)];
|
||||
for l in deckcraft_anim::transition_frame(&tr.kind, &tr.option, p, aspect) {
|
||||
let old_or_black = |t: Option<&TextureHandle>| match t {
|
||||
Some(o) => quad_mesh(Some(o.id()), srect, &l, Color32::WHITE),
|
||||
None => quad_mesh(None, srect, &l, Color32::BLACK),
|
||||
};
|
||||
let mesh = match l.source {
|
||||
Source::Old => old_or_black(tr.old.as_ref()),
|
||||
Source::New => quad_mesh(Some(new.id()), srect, &l, Color32::WHITE),
|
||||
Source::Black => quad_mesh(None, srect, &l, Color32::BLACK),
|
||||
Source::White => quad_mesh(None, srect, &l, Color32::WHITE),
|
||||
Source::OldBack => old_or_black(parts[0].as_ref().or(tr.old.as_ref())),
|
||||
Source::NewBack => quad_mesh(Some(parts[2].as_ref().unwrap_or(new).id()), srect, &l, Color32::WHITE),
|
||||
Source::OldContent => match &parts[1] {
|
||||
Some(t) => quad_mesh(Some(t.id()), srect, &l, Color32::WHITE),
|
||||
None => continue,
|
||||
},
|
||||
Source::NewContent => match &parts[3] {
|
||||
Some(t) => quad_mesh(Some(t.id()), srect, &l, Color32::WHITE),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
painter.add(egui::Shape::mesh(mesh));
|
||||
}
|
||||
drew = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
drew
|
||||
}
|
||||
|
||||
fn draw_videos(app: &mut SlideApp, show: &mut Show, f: &Fr, sl: &Sl, painter: &egui::Painter, tex: &Option<TextureHandle>) -> (Vec<ShapeId>, bool) {
|
||||
let Fr { doc, full, srect, .. } = *f;
|
||||
if let Some(tex) = &tex {
|
||||
painter.image(tex.id(), srect, show.zoom_uv(), Color32::WHITE);
|
||||
}
|
||||
// Playing video: the current frame in its box, or full screen.
|
||||
let (sw, sh) = (doc.slide_size.width as f32, doc.slide_size.height as f32);
|
||||
let uv = show.zoom_uv();
|
||||
let mut framed: Vec<ShapeId> = vec![];
|
||||
let mut full_screen = false;
|
||||
for (id, x, clip) in &sl.media {
|
||||
if !clip.video || sl.hidden.contains(id) {
|
||||
continue;
|
||||
}
|
||||
let r = if clip.full_screen && app.media.is_playing(*id) {
|
||||
full_screen = true;
|
||||
full
|
||||
} else {
|
||||
slide_box(srect, uv, (sw, sh), (x.x as f32, x.y as f32, x.w as f32, x.h as f32))
|
||||
};
|
||||
if crate::media::paint_frame(painter, &app.media, *id, r, clip.crop) {
|
||||
framed.push(*id);
|
||||
}
|
||||
}
|
||||
(framed, full_screen)
|
||||
}
|
||||
|
||||
fn draw_overlay(show: &mut Show, f: &Fr, sl: &Sl, painter: &egui::Painter, framed: &[ShapeId], full_screen: bool) {
|
||||
let Fr { ctx, doc, srect, .. } = *f;
|
||||
let Sl { idx, step, animating, size, .. } = *sl;
|
||||
let uv = show.zoom_uv();
|
||||
// Shapes stacked above a playing video show over it, as in PowerPoint (#1005); a video
|
||||
// playing full screen covers the whole slide.
|
||||
let skip = if full_screen { vec![] } else { below_playing_video(doc, idx, framed) };
|
||||
if skip.is_empty() {
|
||||
show.overlay = None;
|
||||
} else {
|
||||
let fresh = !animating && show.overlay.as_ref().is_some_and(|(k, s, _)| *k == sl.key && *s == skip);
|
||||
if !fresh {
|
||||
let tl = show.timeline(doc).clone();
|
||||
let ci = render_overlay(doc, idx, tl, step, sl.t, size, sl.hidden.clone(), &skip, &sl.times, render_threads(), &sl.plays, &sl.stamps);
|
||||
let handle = match show.overlay.take() {
|
||||
Some((_, _, mut h)) => {
|
||||
crate::textures::replace(ctx, &mut h, ci);
|
||||
h
|
||||
}
|
||||
None => crate::textures::upload(ctx, "show-overlay", ci),
|
||||
};
|
||||
show.overlay = Some((sl.key.clone(), skip, handle));
|
||||
}
|
||||
if let Some((_, _, h)) = &show.overlay {
|
||||
painter.image(h.id(), srect, uv, Color32::WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_media_controls(app: &mut SlideApp, ui: &mut Ui, show: &mut Show, f: &Fr, sl: &Sl, painter: &egui::Painter) {
|
||||
let Fr { ctx, doc, doc_uid, now, srect, .. } = *f;
|
||||
let sw = doc.slide_size.width as f32;
|
||||
// Play/pause on the animated GIF under the pointer, and the audio/video bar.
|
||||
// Slide Show ▸ Show Media Controls hides both (the click still sl.plays a clip).
|
||||
// Not while zoomed: the controls sit where the whole slide would be.
|
||||
if doc.show.show_media_controls && !show.zoom_arm && show.zoom.is_none() {
|
||||
let k = srect.width() / sw.max(1.0);
|
||||
if let Some(p) = ctx.input(|i| i.pointer.hover_pos()).filter(|_| !show.pen && !show.laser && show.pointer_awake(now)) {
|
||||
for g in &sl.gifs {
|
||||
let r = on_screen(srect, k, &g.rect);
|
||||
if r.contains(p) {
|
||||
let b = crate::media::gif_button_rect(r);
|
||||
crate::media::paint_gif_button(painter, b, g.paused, b.contains(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((id, clip, bar)) = media_bar_target(ctx, doc, show, srect).filter(|_| show.pointer_awake(now)) {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(500));
|
||||
let action = {
|
||||
let status = app.media.status(id);
|
||||
crate::media::control_bar(ui, bar, status.as_ref(), &clip)
|
||||
};
|
||||
let pos = app.media.status(id).map(|s| s.position).unwrap_or(0.0);
|
||||
match action {
|
||||
Some(crate::media::BarAction::Toggle) => app.media.toggle(doc_uid, doc, id, &clip, crate::media::Owner::Show),
|
||||
Some(crate::media::BarAction::Seek(t)) => app.media.seek(doc_uid, doc, id, &clip, t, crate::media::Owner::Show),
|
||||
Some(crate::media::BarAction::Nudge(dt)) => app.media.seek(doc_uid, doc, id, &clip, (pos + dt).max(0.0), crate::media::Owner::Show),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_slide(app: &mut SlideApp, ui: &mut Ui, show: &mut Show, f: &Fr, painter: &egui::Painter) {
|
||||
let Fr { ctx, full, srect, .. } = *f;
|
||||
let ppp = ctx.pixels_per_point();
|
||||
// A huge screen or DPI mustn't ask for a picture the GPU can't hold: render at most its limit
|
||||
// (drawn stretched to the screen).
|
||||
let size = crate::textures::fit_size(
|
||||
((srect.width() * ppp).round().clamp(1.0, u32::MAX as f32) as u32, (srect.height() * ppp).round().clamp(1.0, u32::MAX as f32) as u32),
|
||||
crate::textures::max_side(&ctx),
|
||||
crate::textures::max_side(ctx),
|
||||
);
|
||||
if show.ended {
|
||||
painter.text(
|
||||
@@ -205,237 +529,23 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
Color32::from_gray(200),
|
||||
);
|
||||
} else {
|
||||
let idx = show.state.slide;
|
||||
let step = show.state.step;
|
||||
// A main step or a triggered animation is running: the picture changes every frame.
|
||||
let animating = show.state.playing || show.state.trigger_playing();
|
||||
let t = if show.state.playing { now - show.step_start } else { 0.0 };
|
||||
let plays = show.state.trigger_plays();
|
||||
let stamps = show.state.trigger_stamps();
|
||||
let media = crate::media::slide_media(&doc, idx);
|
||||
let hidden: Vec<ShapeId> = media
|
||||
.iter()
|
||||
.filter(|(id, _, clip)| {
|
||||
clip.hide_while_not_playing && !app.media.status(*id).is_some_and(|s| s.state != deckcraft_media::PlayState::Ended)
|
||||
})
|
||||
.map(|m| m.0)
|
||||
.collect();
|
||||
let gifs = app.media.gifs(doc_uid, &doc, idx, now, &app.session.gif_paused);
|
||||
let times: Vec<_> = gifs.iter().map(|g| (g.id, g.t)).collect();
|
||||
crate::media::gif_repaint(&ctx, &gifs);
|
||||
let gif = crate::media::gif_key(&gifs);
|
||||
let done: Vec<(ShapeId, usize)> = plays.iter().map(|p| (p.trigger, p.step)).collect();
|
||||
let key = Key { slide: idx, step, size, doc: std::sync::Arc::as_ptr(&doc) as usize, hidden: hidden.clone(), plays: done, gif };
|
||||
let back_key = BackKey { slide: idx, size, doc: key.doc };
|
||||
let tex = if !animating && show.tex.as_ref().is_some_and(|(k, _)| *k == key) {
|
||||
show.tex.as_ref().map(|(_, t)| t.clone())
|
||||
} else {
|
||||
// The frame rendered ahead of time, when it is this one (waiting for it beats starting over).
|
||||
let ready = match show.prefetch.take() {
|
||||
Some(p) if p.key == key && !animating => p.rx.recv().ok(),
|
||||
_ => None,
|
||||
};
|
||||
let ci = match ready {
|
||||
Some(ci) => ci,
|
||||
None => {
|
||||
let tl = show.timeline(&doc).clone();
|
||||
// Animating: the backdrop doesn't change, only the shapes over it are drawn.
|
||||
let back = animating.then(|| show.backdrop(&doc, back_key));
|
||||
render_frame(&doc, idx, tl, step, t, size, hidden.clone(), ×, render_threads(), &plays, &stamps, back.as_deref())
|
||||
}
|
||||
};
|
||||
let tex = match show.tex.take() {
|
||||
Some((_, mut h)) => {
|
||||
crate::textures::replace(&ctx, &mut h, ci);
|
||||
h
|
||||
}
|
||||
None => crate::textures::upload(&ctx, "show", ci),
|
||||
};
|
||||
show.tex = Some((key.clone(), tex.clone()));
|
||||
Some(tex)
|
||||
};
|
||||
// While this slide rests, render the next one's first frame (not one with GIFs, whose
|
||||
// frame depends on when it is entered).
|
||||
// A slide with animations gets its backdrop ready while it rests, so they start smoothly.
|
||||
if !animating && !show.timeline(&doc).is_empty() {
|
||||
show.prefetch_backdrop(&doc, back_key);
|
||||
}
|
||||
if !animating && show.trans.is_none() && !show.preview_only {
|
||||
let next = show.state.peek_next(&doc);
|
||||
let wanted = next.filter(|n| app.media.gifs(doc_uid, &doc, *n, now, &app.session.gif_paused).is_empty());
|
||||
if let Some(n) = wanted
|
||||
&& show.prefetch.as_ref().is_none_or(|p| p.key.slide != n || p.key.size != size || p.key.doc != std::sync::Arc::as_ptr(&doc) as usize)
|
||||
{
|
||||
show.prefetch = start_prefetch(&doc, n, size);
|
||||
}
|
||||
}
|
||||
let mut drew = false;
|
||||
let morphing = show.trans.as_ref().is_some_and(|tr| tr.kind == "morph" && tr.from.is_some() && now - tr.start < tr.dur);
|
||||
let tl = if morphing { Some(show.timeline(&doc).clone()) } else { None };
|
||||
let morph = match (&show.trans, tl) {
|
||||
(Some(tr), Some(tl)) => {
|
||||
let p = ((now - tr.start) / tr.dur.max(0.01)).clamp(0.0, 1.0);
|
||||
let hidden_b = |id: ShapeId| {
|
||||
let st = state_for(&tl, id, 0, 0.0, &[], &[]);
|
||||
!st.visible || st.opacity <= 0.0 || hidden.contains(&id)
|
||||
};
|
||||
tr.from
|
||||
.and_then(|i| doc.slides.get(i).map(|a| (i, a)))
|
||||
.zip(doc.slides.get(idx))
|
||||
.map(|((i, a), b)| (i, deckcraft_anim::morph_frame(&doc, a, b, p, &hidden_b)))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let (Some((from, m)), Some(tr)) = (morph, show.trans.as_mut()) {
|
||||
let f = |id: ShapeId| ShapeState { opacity: m.opacity_of(id), ..Default::default() };
|
||||
let threads =
|
||||
if cfg!(target_arch = "wasm32") { 0 } else { std::thread::available_parallelism().map(|n| n.get().min(8) as u16).unwrap_or(0) };
|
||||
let opts =
|
||||
RenderOpts { scale: size.0 as f64 / doc.slide_size.width.max(1.0), size: Some(size), state: Some(&f), threads, ..Default::default() };
|
||||
let text: Vec<deckcraft_render::TextMorph> = m
|
||||
.text
|
||||
.iter()
|
||||
.map(|x| deckcraft_render::TextMorph { old: x.old, new: x.new, from: x.from, to: x.to, chars: x.chars, t: m.mix })
|
||||
.collect();
|
||||
let paths: Vec<deckcraft_render::PathMorph> = m
|
||||
.paths
|
||||
.iter()
|
||||
.map(|x| deckcraft_render::PathMorph { id: x.id, from: x.from.clone(), from_box: x.from_box, to_box: x.to_box, t: m.mix })
|
||||
.collect();
|
||||
let img = deckcraft_render::render_blend(&doc, from, idx, m.mix, &m.shapes, &text, &paths, &opts);
|
||||
let ci = crate::textures::to_color_image(&img, false);
|
||||
let frame = match tr.morph.take() {
|
||||
Some(mut h) => {
|
||||
crate::textures::replace(&ctx, &mut h, ci);
|
||||
h
|
||||
}
|
||||
None => crate::textures::upload(&ctx, "morph", ci),
|
||||
};
|
||||
painter.image(frame.id(), srect, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
tr.morph = Some(frame);
|
||||
drew = true;
|
||||
ctx.request_repaint();
|
||||
} else if let (Some(tr), Some(new)) = (show.trans.as_mut(), &tex) {
|
||||
let p = ((now - tr.start) / tr.dur.max(0.01)).clamp(0.0, 1.0);
|
||||
if p < 1.0 {
|
||||
// Slides split into background and content are rendered once per transition;
|
||||
// every frame only composites cached textures.
|
||||
if tr.parts.is_none() && deckcraft_anim::uses_slide_parts(&tr.kind, &tr.option) {
|
||||
tr.parts = Some(part_textures(&ctx, &doc, tr.from, idx, size));
|
||||
}
|
||||
let aspect = doc.slide_size.width / doc.slide_size.height.max(1.0);
|
||||
let part = |k: usize| tr.parts.as_ref().and_then(|p| p.get(k).cloned().flatten());
|
||||
let parts = [part(0), part(1), part(2), part(3)];
|
||||
for l in deckcraft_anim::transition_frame(&tr.kind, &tr.option, p, aspect) {
|
||||
let old_or_black = |t: Option<&TextureHandle>| match t {
|
||||
Some(o) => quad_mesh(Some(o.id()), srect, &l, Color32::WHITE),
|
||||
None => quad_mesh(None, srect, &l, Color32::BLACK),
|
||||
};
|
||||
let mesh = match l.source {
|
||||
Source::Old => old_or_black(tr.old.as_ref()),
|
||||
Source::New => quad_mesh(Some(new.id()), srect, &l, Color32::WHITE),
|
||||
Source::Black => quad_mesh(None, srect, &l, Color32::BLACK),
|
||||
Source::White => quad_mesh(None, srect, &l, Color32::WHITE),
|
||||
Source::OldBack => old_or_black(parts[0].as_ref().or(tr.old.as_ref())),
|
||||
Source::NewBack => quad_mesh(Some(parts[2].as_ref().unwrap_or(new).id()), srect, &l, Color32::WHITE),
|
||||
Source::OldContent => match &parts[1] {
|
||||
Some(t) => quad_mesh(Some(t.id()), srect, &l, Color32::WHITE),
|
||||
None => continue,
|
||||
},
|
||||
Source::NewContent => match &parts[3] {
|
||||
Some(t) => quad_mesh(Some(t.id()), srect, &l, Color32::WHITE),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
painter.add(egui::Shape::mesh(mesh));
|
||||
}
|
||||
drew = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
if !drew {
|
||||
let sl = begin_slide(app, show, f, size);
|
||||
let tex = frame_texture(show, f, &sl);
|
||||
prefetch_next(app, show, f, &sl);
|
||||
if !draw_transition(show, f, &sl, painter, &tex) {
|
||||
show.trans = None;
|
||||
if let Some(tex) = &tex {
|
||||
painter.image(tex.id(), srect, show.zoom_uv(), Color32::WHITE);
|
||||
}
|
||||
// Playing video: the current frame in its box, or full screen.
|
||||
let (sw, sh) = (doc.slide_size.width as f32, doc.slide_size.height as f32);
|
||||
let uv = show.zoom_uv();
|
||||
let mut framed: Vec<ShapeId> = vec![];
|
||||
let mut full_screen = false;
|
||||
for (id, x, clip) in &media {
|
||||
if !clip.video || hidden.contains(id) {
|
||||
continue;
|
||||
}
|
||||
let r = if clip.full_screen && app.media.is_playing(*id) {
|
||||
full_screen = true;
|
||||
full
|
||||
} else {
|
||||
slide_box(srect, uv, sw, sh, x.x as f32, x.y as f32, x.w as f32, x.h as f32)
|
||||
};
|
||||
if crate::media::paint_frame(&painter, &app.media, *id, r, clip.crop) {
|
||||
framed.push(*id);
|
||||
}
|
||||
}
|
||||
// Shapes stacked above a playing video show over it, as in PowerPoint (#1005); a video
|
||||
// playing full screen covers the whole slide.
|
||||
let skip = if full_screen { vec![] } else { below_playing_video(&doc, idx, &framed) };
|
||||
if skip.is_empty() {
|
||||
show.overlay = None;
|
||||
} else {
|
||||
let fresh = !animating && show.overlay.as_ref().is_some_and(|(k, s, _)| *k == key && *s == skip);
|
||||
if !fresh {
|
||||
let tl = show.timeline(&doc).clone();
|
||||
let ci = render_overlay(&doc, idx, tl, step, t, size, hidden.clone(), &skip, ×, render_threads(), &plays, &stamps);
|
||||
let handle = match show.overlay.take() {
|
||||
Some((_, _, mut h)) => {
|
||||
crate::textures::replace(&ctx, &mut h, ci);
|
||||
h
|
||||
}
|
||||
None => crate::textures::upload(&ctx, "show-overlay", ci),
|
||||
};
|
||||
show.overlay = Some((key.clone(), skip, handle));
|
||||
}
|
||||
if let Some((_, _, h)) = &show.overlay {
|
||||
painter.image(h.id(), srect, uv, Color32::WHITE);
|
||||
}
|
||||
}
|
||||
// Play/pause on the animated GIF under the pointer, and the audio/video bar.
|
||||
// Slide Show ▸ Show Media Controls hides both (the click still plays a clip).
|
||||
// Not while zoomed: the controls sit where the whole slide would be.
|
||||
if doc.show.show_media_controls && !show.zoom_arm && show.zoom.is_none() {
|
||||
let k = srect.width() / sw.max(1.0);
|
||||
if let Some(p) = ctx.input(|i| i.pointer.hover_pos()).filter(|_| !show.pen && !show.laser && show.pointer_awake(now)) {
|
||||
for g in &gifs {
|
||||
let r = on_screen(srect, k, &g.rect);
|
||||
if r.contains(p) {
|
||||
let b = crate::media::gif_button_rect(r);
|
||||
crate::media::paint_gif_button(&painter, b, g.paused, b.contains(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((id, clip, bar)) = media_bar_target(&ctx, &doc, &show, srect).filter(|_| show.pointer_awake(now)) {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(500));
|
||||
let action = {
|
||||
let status = app.media.status(id);
|
||||
crate::media::control_bar(ui, bar, status.as_ref(), &clip)
|
||||
};
|
||||
let pos = app.media.status(id).map(|s| s.position).unwrap_or(0.0);
|
||||
match action {
|
||||
Some(crate::media::BarAction::Toggle) => app.media.toggle(doc_uid, &doc, id, &clip, crate::media::Owner::Show),
|
||||
Some(crate::media::BarAction::Seek(t)) => app.media.seek(doc_uid, &doc, id, &clip, t, crate::media::Owner::Show),
|
||||
Some(crate::media::BarAction::Nudge(dt)) => {
|
||||
app.media.seek(doc_uid, &doc, id, &clip, (pos + dt).max(0.0), crate::media::Owner::Show)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (framed, full_screen) = draw_videos(app, show, f, &sl, painter, &tex);
|
||||
draw_overlay(show, f, &sl, painter, &framed, full_screen);
|
||||
draw_media_controls(app, ui, show, f, &sl, painter);
|
||||
}
|
||||
if animating {
|
||||
if sl.animating {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_pen_and_hints(ui: &mut Ui, show: &mut Show, f: &Fr, painter: &egui::Painter, pointing: bool) {
|
||||
let Fr { ctx, doc, now, full, srect, .. } = *f;
|
||||
if let Some(c) = show.blank {
|
||||
painter.rect_filled(full, CornerRadius::ZERO, c);
|
||||
}
|
||||
@@ -500,6 +610,10 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
Color32::WHITE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_bars(ui: &mut Ui, show: &mut Show, f: &Fr, painter: &egui::Painter, out: &mut Out) {
|
||||
let Fr { ctx, doc, now, full, .. } = *f;
|
||||
// Reading view bar / on-screen controls.
|
||||
if show.reading {
|
||||
let bar = Rect::from_min_max(pos2(full.min.x, full.max.y - 34.0), full.max);
|
||||
@@ -517,17 +631,17 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
.layout(egui::Layout::right_to_left(egui::Align::Center)),
|
||||
);
|
||||
if child.button("Close").clicked() {
|
||||
keep = false;
|
||||
out.keep = false;
|
||||
}
|
||||
if child.button("▶").clicked() {
|
||||
let a = show.state.next(&doc);
|
||||
if !show.apply(a, &doc, now) {
|
||||
keep = false;
|
||||
let a = show.state.next(doc);
|
||||
if !show.apply(a, doc, now) {
|
||||
out.keep = false;
|
||||
}
|
||||
}
|
||||
if child.button("◀").clicked() {
|
||||
let a = show.state.prev(&doc);
|
||||
show.apply(a, &doc, now);
|
||||
let a = show.state.prev(doc);
|
||||
show.apply(a, doc, now);
|
||||
}
|
||||
} else if !show.grid && (show.pointer_awake(now) || show.zoom_arm) {
|
||||
let bar = tool_bar_rect(full);
|
||||
@@ -536,13 +650,13 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
ui.new_child(egui::UiBuilder::new().max_rect(bar.shrink2(vec2(10.0, 4.0))).layout(egui::Layout::left_to_right(egui::Align::Center)));
|
||||
let b = |ui: &mut Ui, s: &str| ui.add(egui::Button::new(egui::RichText::new(s).color(Color32::WHITE).size(15.0)).frame(false));
|
||||
if b(&mut child, "◀").clicked() {
|
||||
let a = show.state.prev(&doc);
|
||||
show.apply(a, &doc, now);
|
||||
let a = show.state.prev(doc);
|
||||
show.apply(a, doc, now);
|
||||
}
|
||||
if b(&mut child, "▶").clicked() {
|
||||
let a = show.state.next(&doc);
|
||||
if !show.apply(a, &doc, now) {
|
||||
keep = false;
|
||||
let a = show.state.next(doc);
|
||||
if !show.apply(a, doc, now) {
|
||||
out.keep = false;
|
||||
}
|
||||
}
|
||||
if b(&mut child, if show.pen { "✏ on" } else { "✏" }).clicked() {
|
||||
@@ -552,10 +666,10 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
show.set_laser(!show.laser);
|
||||
}
|
||||
if b(&mut child, "Black").clicked() {
|
||||
toggle_blank(&mut show, Color32::BLACK);
|
||||
toggle_blank(show, Color32::BLACK);
|
||||
}
|
||||
if b(&mut child, "White").clicked() {
|
||||
toggle_blank(&mut show, Color32::WHITE);
|
||||
toggle_blank(show, Color32::WHITE);
|
||||
}
|
||||
if b(&mut child, "All").clicked() {
|
||||
show.grid = !show.grid;
|
||||
@@ -564,12 +678,16 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
show.zoom_key();
|
||||
}
|
||||
if b(&mut child, "End").clicked() {
|
||||
keep = false;
|
||||
out.keep = false;
|
||||
}
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_extras(show: &mut Show, f: &Fr, painter: &egui::Painter, out: &mut Out) {
|
||||
let Fr { ctx, doc, now, full, .. } = *f;
|
||||
if show.grid && !show.ended {
|
||||
paint_grid(&painter, &ctx, &mut show, &doc, full);
|
||||
paint_grid(painter, ctx, show, doc, full);
|
||||
}
|
||||
if show.rehearse {
|
||||
let t = now - show.slide_start;
|
||||
@@ -586,9 +704,13 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(100));
|
||||
}
|
||||
// Presenter view in a second window.
|
||||
if keep && show.presenter && !show.reading {
|
||||
presenter_window(&ctx, &mut show, &doc, now, &mut keep);
|
||||
if out.keep && show.presenter && !show.reading {
|
||||
presenter_window(ctx, show, doc, now, &mut out.keep);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(app: &mut SlideApp, mut show: Show, f: &Fr, keep: bool) {
|
||||
let Fr { ctx, now, .. } = *f;
|
||||
if keep {
|
||||
app.show = Some(show);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::presenter::{NOTES_MAX, NOTES_MIN};
|
||||
use super::render::render_thumb;
|
||||
use super::*;
|
||||
use super::{Align2, Color32, CornerRadius, Pos2, Presentation, Rect, Show, Stroke, json, pos2, theme, vec2};
|
||||
|
||||
impl Show {
|
||||
pub(super) fn zoom_uv(&self) -> Rect {
|
||||
@@ -344,7 +344,7 @@ pub(super) fn slide_to_screen(srect: Rect, uv: Rect, nx: f32, ny: f32) -> Pos2 {
|
||||
pos2(srect.min.x + u * srect.width(), srect.min.y + v * srect.height())
|
||||
}
|
||||
|
||||
pub(super) fn slide_box(srect: Rect, uv: Rect, slide_w: f32, slide_h: f32, x: f32, y: f32, w: f32, h: f32) -> Rect {
|
||||
pub(super) fn slide_box(srect: Rect, uv: Rect, (slide_w, slide_h): (f32, f32), (x, y, w, h): (f32, f32, f32, f32)) -> Rect {
|
||||
let a = slide_to_screen(srect, uv, x / slide_w.max(1.0), y / slide_h.max(1.0));
|
||||
let b = slide_to_screen(srect, uv, (x + w) / slide_w.max(1.0), (y + h) / slide_h.max(1.0));
|
||||
Rect::from_min_max(a, b)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, LayoutId, MasterId, Serialize, SlideId, Target, qat, theme};
|
||||
|
||||
/// The operation the user asked for. Kept with the request so a late completion does not
|
||||
/// guess a command from the file name.
|
||||
|
||||
@@ -65,15 +65,13 @@ impl Textures {
|
||||
}
|
||||
|
||||
/// A SmartArt layout drawn small for a gallery (`nodes` for a preview of the selected
|
||||
/// graphic's own outline), cached; drawn with the active presentation's theme.
|
||||
/// graphic's own outline), cached; `look` is the layout kind, style and colours; drawn with the active presentation's theme.
|
||||
pub fn smartart(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
p: &deckcraft_model::Presentation,
|
||||
slide: usize,
|
||||
kind: &str,
|
||||
style: &str,
|
||||
colors: &str,
|
||||
(kind, style, colors): (&str, &str, &str),
|
||||
nodes: Option<&[deckcraft_model::DiagramNode]>,
|
||||
width_px: u32,
|
||||
) -> Option<TextureHandle> {
|
||||
|
||||
Reference in New Issue
Block a user