mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-14 06:02:48 +00:00
fix: add hand tool scale buttons
This commit is contained in:
1 parent
6197270230
commit
8e09dcf655
8 files changed
+102
-5
No files matched your search
@@ -620,3 +620,38 @@ fn options_bar_reaches_brush_settings_and_the_preset_library() {
|
||||
h.run_steps(3);
|
||||
assert!(h.state().ui.panels.brush_settings && h.state().ui.brush_tab == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hand_tool_offers_scale_buttons_and_requests_the_right_view() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
||||
let mut app = app();
|
||||
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
|
||||
app.sync_views();
|
||||
app.ui.tool = crate::state::Tool::Hand;
|
||||
app.ui.views[0].zoom = 2.0;
|
||||
let mut h = Harness::builder().with_size(vec2(1400.0, 60.0)).build_ui_state(
|
||||
|ui, app: &mut PhotocraftApp| {
|
||||
if ui.ctx().fonts(|f| f.families().contains(&egui::FontFamily::Name("medium".into()))) {
|
||||
crate::panels::options_bar(app, ui);
|
||||
}
|
||||
},
|
||||
app,
|
||||
);
|
||||
PhotocraftApp::setup_context(&h.ctx, crate::theme::ThemeKind::Studio);
|
||||
h.run_steps(4);
|
||||
|
||||
h.get_by_label("100%").click();
|
||||
h.run_steps(1);
|
||||
assert_eq!(h.state().ui.views[0].zoom, 1.0);
|
||||
|
||||
h.get_by_label("Fit Screen").click();
|
||||
h.run_steps(1);
|
||||
assert!(h.state().ui.views[0].fit_pending);
|
||||
assert!(!h.state().ui.views[0].fill_pending);
|
||||
|
||||
h.get_by_label("Fill Screen").click();
|
||||
h.run_steps(1);
|
||||
assert!(h.state().ui.views[0].fill_pending);
|
||||
assert!(!h.state().ui.views[0].fit_pending);
|
||||
}
|
||||
@@ -394,6 +394,18 @@ pub fn fit_view(view: &mut View, doc: &Document, area: Vec2) {
|
||||
view.zoom = zoom;
|
||||
view.center = [w / 2.0, h / 2.0];
|
||||
view.fit_pending = false;
|
||||
view.fill_pending = false;
|
||||
}
|
||||
|
||||
/// Centre the document and zoom until it fills the canvas. One axis can extend beyond the
|
||||
/// viewport, matching the Hand tool's Fill Screen action.
|
||||
pub fn fill_view(view: &mut View, doc: &Document, area: Vec2) {
|
||||
let (w, h) = (doc.size.width as f32, doc.size.height as f32);
|
||||
let zoom = (area.x / w).max(area.y / h).clamp(0.01, 32.0);
|
||||
view.zoom = zoom;
|
||||
view.center = [w / 2.0, h / 2.0];
|
||||
view.fit_pending = false;
|
||||
view.fill_pending = false;
|
||||
}
|
||||
|
||||
/// Zoom steps like Photoshop's (⌘+ / ⌘−).
|
||||
@@ -1545,6 +1557,9 @@ pub fn canvas_view(app: &mut PhotocraftApp, ui: &mut egui::Ui, idx: usize, rect:
|
||||
if view.fit_pending && rect.width() > 50.0 {
|
||||
fit_view(&mut view, &doc, rect.size());
|
||||
}
|
||||
if view.fill_pending && rect.width() > 50.0 {
|
||||
fill_view(&mut view, &doc, rect.size());
|
||||
}
|
||||
// Preferences › Tools › Overscroll off: clamp before anything is drawn (scrollbars.rs).
|
||||
if !app.session.prefs().tools.overscroll && crate::scrollbars::clamp_view(&mut view, rect.size()) {
|
||||
ctx.request_repaint();
|
||||
@@ -3534,6 +3549,20 @@ mod tests {
|
||||
assert_eq!(zoom_step(32.0, 1), 32.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_view_covers_the_canvas_and_centres_the_document() {
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
|
||||
let doc = app.session.active().unwrap().doc.clone();
|
||||
let mut view = View { fill_pending: true, ..View::default() };
|
||||
|
||||
fill_view(&mut view, &doc, vec2(600.0, 600.0));
|
||||
|
||||
assert_eq!(view.zoom, 3.0, "the short edge fills the available height");
|
||||
assert_eq!(view.center, [200.0, 100.0]);
|
||||
assert!(!view.fill_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damage_grows_by_nested_effect_reach() {
|
||||
use photocraft_doc::{Effect, Layer};
|
||||
|
||||
@@ -295,15 +295,18 @@ fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, req: &ControlRequest)
|
||||
if let Some(z) = p.get("zoom").and_then(Value::as_f64) {
|
||||
app.ui.views[i].zoom = (z as f32).clamp(0.01, 64.0);
|
||||
app.ui.views[i].fit_pending = false;
|
||||
app.ui.views[i].fill_pending = false;
|
||||
}
|
||||
if let Some(c) = p.get("center").and_then(Value::as_array)
|
||||
&& c.len() == 2
|
||||
{
|
||||
app.ui.views[i].center = [c[0].as_f64().unwrap_or(0.0) as f32, c[1].as_f64().unwrap_or(0.0) as f32];
|
||||
app.ui.views[i].fit_pending = false;
|
||||
app.ui.views[i].fill_pending = false;
|
||||
}
|
||||
if p.get("fit").and_then(Value::as_bool) == Some(true) {
|
||||
app.ui.views[i].fit_pending = true;
|
||||
app.ui.views[i].fill_pending = false;
|
||||
}
|
||||
}
|
||||
if let Some(name) = s("theme") {
|
||||
|
||||
@@ -447,7 +447,7 @@ mod tests {
|
||||
fn space_on_the_canvas_moves_the_frame_not_the_view() {
|
||||
use egui::{Event, Key, PointerButton, pos2};
|
||||
let mut a = app(SampleType::U8);
|
||||
let view = crate::state::View { zoom: 2.0, center: [100.0, 50.0], fit_pending: false, doc_size: [200, 100] };
|
||||
let view = crate::state::View { zoom: 2.0, center: [100.0, 50.0], fit_pending: false, fill_pending: false, doc_size: [200, 100] };
|
||||
a.ui.views = vec![view.clone()];
|
||||
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(600.0, 400.0)).build_ui_state(
|
||||
|ui, app: &mut PhotocraftApp| {
|
||||
|
||||
@@ -268,6 +268,12 @@ pub(crate) fn invoke_unguarded(app: &mut PhotocraftApp, ctx: &egui::Context, id:
|
||||
"view.fitOnScreen" => v.fit_pending = true,
|
||||
_ => v.zoom = 1.0,
|
||||
}
|
||||
if id == "view.fitOnScreen" {
|
||||
v.fill_pending = false;
|
||||
} else {
|
||||
v.fit_pending = false;
|
||||
v.fill_pending = false;
|
||||
}
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"window.newWindowForDocument" => {
|
||||
|
||||
@@ -883,7 +883,28 @@ pub fn options_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
|
||||
app.ui.views[i].zoom = 1.0;
|
||||
}
|
||||
}
|
||||
Tool::Hand => hint(ui, tl!("Drag to pan · hold Space with any tool")),
|
||||
Tool::Hand => {
|
||||
hint(ui, tl!("Drag to pan · hold Space with any tool"));
|
||||
if widgets::secondary_button(ui, "100%", 0.0).clicked()
|
||||
&& let Some(i) = app.session.active_index()
|
||||
{
|
||||
app.ui.views[i].zoom = 1.0;
|
||||
app.ui.views[i].fit_pending = false;
|
||||
app.ui.views[i].fill_pending = false;
|
||||
}
|
||||
if widgets::secondary_button(ui, tl!("Fit Screen"), 0.0).clicked()
|
||||
&& let Some(i) = app.session.active_index()
|
||||
{
|
||||
app.ui.views[i].fit_pending = true;
|
||||
app.ui.views[i].fill_pending = false;
|
||||
}
|
||||
if widgets::secondary_button(ui, tl!("Fill Screen"), 0.0).clicked()
|
||||
&& let Some(i) = app.session.active_index()
|
||||
{
|
||||
app.ui.views[i].fill_pending = true;
|
||||
app.ui.views[i].fit_pending = false;
|
||||
}
|
||||
}
|
||||
Tool::Lasso | Tool::PolygonLasso => hint(
|
||||
ui,
|
||||
&crate::i18n::fmt(
|
||||
|
||||
@@ -280,7 +280,7 @@ mod tests {
|
||||
assert_eq!(clamp_axis(600.0, 1000.0, 500.0, 1.0), 600.0);
|
||||
// Smaller than the view: centred.
|
||||
assert_eq!(clamp_axis(-90.0, 1000.0, 500.0, 0.2), 500.0);
|
||||
let mut v = View { zoom: 1.0, center: [-500.0, 20.0], fit_pending: false, doc_size: [1000, 800] };
|
||||
let mut v = View { zoom: 1.0, center: [-500.0, 20.0], fit_pending: false, fill_pending: false, doc_size: [1000, 800] };
|
||||
assert!(clamp_view(&mut v, vec2(500.0, 400.0)));
|
||||
assert_eq!(v.center, [250.0, 200.0]);
|
||||
assert!(!clamp_view(&mut v, vec2(500.0, 400.0)), "stable: no repaint loop");
|
||||
@@ -299,7 +299,7 @@ mod tests {
|
||||
let (at, len) = s.thumb((0.0, 1000.0), 0.0);
|
||||
assert!(at.is_finite() && len.is_finite());
|
||||
assert_eq!(s.doc_per_point((0.0, 1000.0), 0.0), 0.0);
|
||||
let mut v = View { zoom: 1.0, center: [0.0, 0.0], fit_pending: false, doc_size: [0, 0] };
|
||||
let mut v = View { zoom: 1.0, center: [0.0, 0.0], fit_pending: false, fill_pending: false, doc_size: [0, 0] };
|
||||
assert!(!clamp_view(&mut v, vec2(500.0, 400.0)));
|
||||
}
|
||||
|
||||
|
||||
@@ -367,6 +367,9 @@ pub struct View {
|
||||
pub center: [f32; 2],
|
||||
/// Recompute fit-to-screen on next frame.
|
||||
pub fit_pending: bool,
|
||||
/// Recompute fill-screen on next frame. Unlike fit, this may crop an edge of the document.
|
||||
#[serde(default)]
|
||||
pub fill_pending: bool,
|
||||
/// Document size this view last showed; a change (Image/Canvas Size, crop) re-centres it.
|
||||
#[serde(default)]
|
||||
pub doc_size: [u32; 2],
|
||||
@@ -374,7 +377,7 @@ pub struct View {
|
||||
|
||||
impl Default for View {
|
||||
fn default() -> Self {
|
||||
Self { zoom: 1.0, center: [0.0, 0.0], fit_pending: true, doc_size: [0, 0] }
|
||||
Self { zoom: 1.0, center: [0.0, 0.0], fit_pending: true, fill_pending: false, doc_size: [0, 0] }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user