mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-05 01:58:58 +00:00
Merge PR #3229
This commit is contained in:
commit
cb4b7af1e3
5 files changed
+89
-7
No files matched your search
@@ -316,11 +316,12 @@ pub(crate) fn paint_objects(app: &SheetApp, p: &Painter, geo: &Geo, wb: &Workboo
|
||||
p.add(PathShape::convex_polygon(vec![pos2(r.center().x, r.top()), r.right_bottom(), r.left_bottom()], fill, Stroke::new(1.0, line)));
|
||||
}
|
||||
ShapeKind::Ink => {
|
||||
let w = gridcraft_engine::cmd::draw::ink_width(sp) * geo.z;
|
||||
let pt = gridcraft_engine::cmd::draw::ink_width(sp);
|
||||
let w = pt * geo.z;
|
||||
let pts: Vec<Pos2> =
|
||||
gridcraft_engine::cmd::draw::ink_points(sp).iter().map(|q| pos2(r.left() + q[0] * geo.z, r.top() + q[1] * geo.z)).collect();
|
||||
if pts.len() >= 2 {
|
||||
let col = if w > 8.0 { line.gamma_multiply(0.45) } else { line };
|
||||
let col = ink_color(line, pt);
|
||||
p.add(PathShape::line(pts, PathStroke::new(w, col)));
|
||||
}
|
||||
label = false;
|
||||
@@ -556,3 +557,9 @@ pub fn hit(app: &SheetApp, geo: &Geo, sh: &Sheet, p: Pos2) -> Option<(ObjRef, Re
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The colour ink is painted in: a highlighter (a stroke over 8 pt wide) is translucent, by its
|
||||
/// width in points, so the live pen stroke and the committed ink agree at every zoom.
|
||||
pub(crate) fn ink_color(line: Color32, width_pt: f32) -> Color32 {
|
||||
if width_pt > 8.0 { line.gamma_multiply(0.45) } else { line }
|
||||
}
|
||||
@@ -225,10 +225,10 @@ fn handle_draw_tool(app: &mut SheetApp, input: &InteractionInput<'_>) -> bool {
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
input
|
||||
.ui
|
||||
.painter()
|
||||
.add(egui::epaint::PathShape::line(pts, egui::epaint::PathStroke::new(2.0 * geo.z, Color32::from_rgb(0x1F, 0x5F, 0xC9))));
|
||||
// The line under the pointer is the ink the stroke will commit (#1786).
|
||||
let theme = app.session.active().map(|d| d.wb.theme.clone()).unwrap_or_default();
|
||||
let (color, width) = pen_ink(&app.session.draw_color, app.session.draw_width, &theme);
|
||||
input.ui.painter().add(egui::epaint::PathShape::line(pts, egui::epaint::PathStroke::new(width * geo.z, color)));
|
||||
}
|
||||
if resp.drag_stopped() && app.grid.ink.len() >= 2 {
|
||||
let pts: Vec<[f32; 2]> = std::mem::take(&mut app.grid.ink);
|
||||
@@ -249,6 +249,17 @@ fn handle_draw_tool(app: &mut SheetApp, input: &InteractionInput<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The colour and width (points) of the ink a pen stroke commits, as `draw.stroke` stores it (the
|
||||
/// pen's colour, pen blue by default; its width, 2 pt by default, kept to 0.5–40 pt and stored
|
||||
/// whole) and as Ink shapes paint it (a highlighter, over 8 pt, is translucent).
|
||||
pub(crate) fn pen_ink(color: &str, width: f32, theme: &gridcraft_engine::model::Theme) -> (Color32, f32) {
|
||||
let pen_blue = gridcraft_engine::model::Color::rgb(0x1F, 0x5F, 0xC9);
|
||||
let line = gridcraft_engine::model::Color::from_hex(color).unwrap_or(pen_blue);
|
||||
let width = if width <= 0.0 { 2.0 } else { width }.clamp(0.5, 40.0).round().max(1.0);
|
||||
let color = line.resolve(theme).map(crate::theme::color32).unwrap_or(Color32::from_rgb(0x1F, 0x5F, 0xC9));
|
||||
(crate::chartview::ink_color(color, width), width)
|
||||
}
|
||||
|
||||
fn handle_header_autofit(app: &mut SheetApp, input: &InteractionInput<'_>) -> bool {
|
||||
let (geo, sh, sel, pos) = (input.geo, input.sheet, &input.selection, input.position);
|
||||
let (in_col_header, in_row_header) = (input.in_column_header, input.in_row_header);
|
||||
@@ -1044,3 +1055,20 @@ pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, exte
|
||||
ed.point_anchor = Some((sheet, anchor));
|
||||
app.grid.ensure_visible = true;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_pen_preview_is_the_ink_the_stroke_commits() {
|
||||
// #1786: the preview was always blue at 2 pt, whatever the pen.
|
||||
let theme = gridcraft_engine::model::Theme::default();
|
||||
assert_eq!(pen_ink("#C00000", 3.0, &theme), (Color32::from_rgb(0xC0, 0, 0), 3.0));
|
||||
assert_eq!(pen_ink("", 0.0, &theme), (Color32::from_rgb(0x1F, 0x5F, 0xC9), 2.0), "no pen chosen yet: pen blue, 2 pt");
|
||||
assert_eq!(pen_ink("not a colour", 0.2, &theme), (Color32::from_rgb(0x1F, 0x5F, 0xC9), 1.0));
|
||||
// A highlighter is wide and translucent, as the committed ink is painted.
|
||||
let (c, w) = pen_ink("#FFFF00", 12.0, &theme);
|
||||
assert_eq!((c, w), (Color32::from_rgb(0xFF, 0xFF, 0).gamma_multiply(0.45), 12.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//! Committed ink is painted as the pen previews it, at any zoom.
|
||||
|
||||
use egui::Shape;
|
||||
use egui::epaint::ColorMode;
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
use serde_json::json;
|
||||
|
||||
/// The colour of the ink stroke drawn from (40, 40) to (200, 40) sheet points, `width` points
|
||||
/// wide: the two-point horizontal path whose width is `width` at its own scale (160 pt long).
|
||||
fn painted_ink(h: &egui_kittest::Harness<'static, SheetApp>, width: f32) -> Option<egui::Color32> {
|
||||
h.output().shapes.iter().find_map(|s| match &s.shape {
|
||||
Shape::Path(p) if p.points.len() == 2 && (p.points[0].y - p.points[1].y).abs() < 0.01 => {
|
||||
let z = (p.points[1].x - p.points[0].x).abs() / 160.0;
|
||||
match p.stroke.color {
|
||||
ColorMode::Solid(c) if z > 0.0 && (p.stroke.width - width * z).abs() < 0.01 => Some(c),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ink_translucency_follows_the_pen_not_the_zoom() {
|
||||
// A highlighter (over 8 pt) is translucent and a pen isn't, whatever the zoom: the committed
|
||||
// stroke used its width on screen, so at 50% a 12 pt highlighter turned opaque and at 200% a
|
||||
// 6 pt pen turned translucent, unlike the pen's preview.
|
||||
for (width, zoom, translucent) in [(12.0_f32, 50, true), (6.0, 200, false), (12.0, 100, true), (6.0, 100, false)] {
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s.execute("view.zoom", json!({"percent": zoom})).unwrap();
|
||||
s.execute("draw.stroke", json!({"points": [[40, 40], [200, 40]], "color": "#FFFF00", "width": width})).unwrap();
|
||||
let mut h = crate::common::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut SheetApp| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
},
|
||||
crate::common::app(s),
|
||||
);
|
||||
h.run_steps(4);
|
||||
let c = painted_ink(&h, width).unwrap_or_else(|| panic!("the {width} pt stroke is painted at {zoom}%"));
|
||||
assert_eq!(c.a() < 255, translucent, "{width} pt at {zoom}%: {c:?}");
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ mod hyperlink_ctrl_click;
|
||||
mod hyperlink_dialog;
|
||||
mod in_cell_editor_size;
|
||||
mod in_cell_editor_wrap;
|
||||
mod ink_render;
|
||||
mod insert_copied_cells;
|
||||
mod interaction;
|
||||
mod interface_scale;
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (copy quotes only cells with a tab or line break, as Excel does; earlier: Ctrl+. Next Corner of Selection; earlier: dragging the selection's border onto data asks before replacing it; earlier: RefEdit reference boxes in dialogs: Goal Seek, Solver and Data Analysis use them, the arrow keys select while a dialog is collapsed and F4 cycles a reference's anchors; earlier: Python cells: Python mode in the formula bar; earlier: Insert › Equation and the equation editor; earlier: every floating object selects, resizes from eight handles and deletes with the keyboard; earlier: Formulas › Error Checking › Trace Error; trace arrows stay through a zoom (#3097); earlier: Page Setup sets the page order, Down, then over or Over, then down, which printing, the print preview, Page Break Preview and Page Layout view follow; earlier: Formulas › Function Library as labelled buttons with category menus; Recently Used and the Insert Function categories; Esc leaves a File-page text field before closing the view; earlier: checkbox cells print as boxes; Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; editing a hyperlink loads its existing fields; AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (the Draw tab's live stroke preview uses the pen's colour and thickness; earlier: copy quotes only cells with a tab or line break, as Excel does; earlier: Ctrl+. Next Corner of Selection; earlier: dragging the selection's border onto data asks before replacing it; earlier: RefEdit reference boxes in dialogs: Goal Seek, Solver and Data Analysis use them, the arrow keys select while a dialog is collapsed and F4 cycles a reference's anchors; earlier: Python cells: Python mode in the formula bar; earlier: Insert › Equation and the equation editor; earlier: every floating object selects, resizes from eight handles and deletes with the keyboard; earlier: Formulas › Error Checking › Trace Error; trace arrows stay through a zoom (#3097); earlier: Page Setup sets the page order, Down, then over or Over, then down, which printing, the print preview, Page Break Preview and Page Layout view follow; earlier: Formulas › Function Library as labelled buttons with category menus; Recently Used and the Insert Function categories; Esc leaves a File-page text field before closing the view; earlier: checkbox cells print as boxes; Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; editing a hyperlink loads its existing fields; AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
|
||||
Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md).
|
||||
**UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured
|
||||
@@ -238,6 +238,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | Draw tab: the stroke drawn under the pointer while dragging a pen is the ink that will be committed, in the pen's colour and thickness (translucent for a highlighter), instead of always blue at 2 pt (#1786) |
|
||||
| 2026-11-03 | minor | Copy puts a cell that holds a quote on the clipboard as it is (`say "hi"`), quoting only text with a tab or line break, as Excel does; pasting text reads a field as quoted only when its closing quote ends it, so `"hi" there` pastes with its quotes (#2225) |
|
||||
| 2026-11-03 | minor | Ctrl+. (Control+. on a Mac): Next Corner of Selection, `selection.nextCorner`: the active cell moves clockwise to the next corner of the selected area, which stays selected, and becomes the anchor, as in Excel (#1442) |
|
||||
| 2026-11-02 | minor | Dragging the selection's border onto cells that hold data asks "There's already data here. Do you want to replace it?" (OK replaces, Cancel leaves the sheet and clipboard as they were) through the new `range.move` command (#3136); the row no longer claims Ctrl/Option copy and Shift insert for the border drag, which are not wired yet |
|
||||
|
||||
Reference in new issue
Block a user