From 25be9264ade6cad8b7ebf5a3f1782ee694fcd564 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 19 May 2025 01:57:33 -0700 Subject: [PATCH 1/8] canvas: basic rendering, selection working --- canvas/manip.go | 13 ++++---- canvas/paint.go | 4 +-- canvas/select.go | 9 ++---- canvas/sprites.go | 4 +-- canvas/svg.go | 66 +++++++++----------------------------- canvas/testdata/rectgp.svg | 15 +-------- 6 files changed, 28 insertions(+), 83 deletions(-) diff --git a/canvas/manip.go b/canvas/manip.go index 46c44b5d..bfa91e50 100644 --- a/canvas/manip.go +++ b/canvas/manip.go @@ -32,7 +32,7 @@ func (sv *SVG) ManipDone() { switch { case es.Action == BoxSelect: bbox := image.Rectangle{Min: es.DragStartPos, Max: es.DragCurPos} - bbox = bbox.Canon().Sub(sv.Geom.ContentBBox.Min) + bbox = bbox.Canon() InactivateSprites(sv, SpRubberBand) fmt.Println(bbox) sel := sv.SelectWithinBBox(bbox, false) @@ -54,7 +54,7 @@ func (sv *SVG) ManipDone() { // GridDots returns the current grid spacing and offsets in dots. func (sv *SVG) GridDots() (float32, math32.Vector2) { - svoff := math32.FromPoint(sv.Geom.ContentBBox.Min) + svoff := math32.Vector2{} // math32.FromPoint(sv.Geom.ContentBBox.Min) grid := sv.GridEff if grid <= 0 { grid = 12 @@ -303,7 +303,6 @@ func (sv *SVG) DragMove(e events.Event) { sv.GatherAlignPoints() } - svoff := math32.FromPoint(sv.Geom.ContentBBox.Min) spt := math32.FromPoint(es.DragStartPos) mpt := math32.FromPoint(e.Pos()) if e.HasAnyModifier(key.Control) { @@ -321,7 +320,7 @@ func (sv *SVG) DragMove(e events.Event) { es.DragSelectEffectiveBBox = sv.SnapBBox(es.DragSelectEffectiveBBox) - pt := es.DragSelectStartBBox.Min.Sub(svoff) + pt := es.DragSelectStartBBox.Min tdel := es.DragSelectEffectiveBBox.Min.Sub(es.DragSelectStartBBox.Min) for itm, ss := range es.Selected { itm.ReadGeom(sv.SVG, ss.InitGeom) @@ -329,7 +328,7 @@ func (sv *SVG) DragMove(e events.Event) { } sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.DragSelectEffectiveBBox) sv.SetSelSpritePos() - go sv.RenderSVG() + sv.NeedsRender() } func SquareBBox(bb math32.Box2) math32.Box2 { @@ -448,7 +447,7 @@ func (sv *SVG) SpriteReshapeDrag(sp Sprites, e events.Event) { sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.DragSelectEffectiveBBox) sv.SetSelSpritePos() - go sv.RenderSVG() + sv.NeedsRender() } // SpriteRotateDrag processes a mouse rotate drag event on a selection sprite @@ -525,5 +524,5 @@ func (sv *SVG) SpriteRotateDrag(sp Sprites, delta image.Point) { sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.DragSelectCurrentBBox) sv.SetSelSpritePos() - go sv.RenderSVG() + sv.NeedsRender() } diff --git a/canvas/paint.go b/canvas/paint.go index 8b790310..dd3de595 100644 --- a/canvas/paint.go +++ b/canvas/paint.go @@ -349,7 +349,7 @@ func (vv *Canvas) ManipAction(act Actions, data string, manip bool, fun func(sii vv.ChangeMade() } } else { - go sv.RenderSVG() + sv.NeedsRender() } } @@ -428,7 +428,7 @@ func (vv *Canvas) SetStrokeWidth(wp string, manip bool) { if !manip { vv.ChangeMade() } else { - go sv.RenderSVG() + sv.NeedsRender() } } diff --git a/canvas/select.go b/canvas/select.go index b9a60036..54a74bfa 100644 --- a/canvas/select.go +++ b/canvas/select.go @@ -214,9 +214,6 @@ func (sv *SVG) SetSelSpritePos() { // SetBBoxSpritePos sets positions of given type of sprites func (sv *SVG) SetBBoxSpritePos(typ Sprites, idx int, bbox math32.Box2) { - bbox.Min.SetAdd(math32.FromPoint(sv.Geom.ContentBBox.Min)) - bbox.Max.SetAdd(math32.FromPoint(sv.Geom.ContentBBox.Min)) - _, spsz := HandleSpriteSize(1) midX := int(0.5 * (bbox.Min.X + bbox.Max.X - float32(spsz.X))) midY := int(0.5 * (bbox.Min.Y + bbox.Max.Y - float32(spsz.Y))) @@ -378,14 +375,13 @@ func (gv *Canvas) SelectRotate(deg float32) { sv := gv.SVG() sv.UndoSave("Rotate", fmt.Sprintf("%g", deg)) - svoff := sv.Geom.ContentBBox.Min del := math32.Vector2{} sc := math32.Vec2(1, 1) rot := math32.DegToRad(deg) for sn := range es.Selected { sng := sn.AsNodeBase() sz := math32.FromPoint(sng.BBox.Size()) - mn := math32.FromPoint(sng.BBox.Min.Sub(svoff)) + mn := math32.FromPoint(sng.BBox.Min) ctr := mn.Add(sz.MulScalar(.5)) sn.ApplyDeltaTransform(sv.SVG, del, sc, rot, ctr) } @@ -401,7 +397,7 @@ func (gv *Canvas) SelectScale(scx, scy float32) { sv := gv.SVG() sv.UndoSave("Scale", fmt.Sprintf("%g,%g", scx, scy)) - svoff := sv.Geom.ContentBBox.Min + svoff := image.Point{} // sv.Geom.ContentBBox.Min del := math32.Vector2{} sc := math32.Vec2(scx, scy) for sn := range es.Selected { @@ -628,7 +624,6 @@ func (sv *SVG) SelectWithinBBox(bbox image.Rectangle, leavesOnly bool) []svg.Nod // if excludeSel, any leaf nodes that are within the current edit selection are // excluded, func (sv *SVG) SelectContainsPoint(pt image.Point, leavesOnly, excludeSel bool) svg.Node { - pt = pt.Sub(sv.Geom.ContentBBox.Min) es := sv.EditState() var curlay tree.Node fn := es.FirstSelectedNode() diff --git a/canvas/sprites.go b/canvas/sprites.go index 294b885e..a75bc9ec 100644 --- a/canvas/sprites.go +++ b/canvas/sprites.go @@ -239,7 +239,7 @@ func InactivateSprites(ctx core.Widget, typ Sprites) { // Sprite rendering var ( - HandleSpriteScale = float32(18) + HandleSpriteScale = float32(12) HandleSizeMin = 4 HandleBorderMin = 2 ) @@ -318,7 +318,7 @@ func DrawSpriteNodeCtrl(sp *core.Sprite, subtyp Sprites) { } var ( - LineSpriteScale = float32(8) + LineSpriteScale = float32(4) LineSizeMin = 3 LineBorderMin = 1 ) diff --git a/canvas/svg.go b/canvas/svg.go index 49d95300..228a4c22 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -176,7 +176,7 @@ func (sv *SVG) Init() { del := e.PrevDelta() sv.SVG.Translate.X += float32(del.X) sv.SVG.Translate.Y += float32(del.Y) - go sv.RenderSVG() + sv.NeedsRender() return } if es.HasSelected() { @@ -216,18 +216,16 @@ func (sv *SVG) Init() { sv.ManipDone() return } - if e.MouseButton() == events.Left { - // release on select -- do extended selection processing - if (es.SelectNoDrag && es.Tool == SelectTool) || (es.Tool != SelectTool && ToolDoesBasicSelect(es.Tool)) { - es.SelectNoDrag = false - e.SetHandled() - if sob == nil { - sob = sv.SelectContainsPoint(e.Pos(), false, false) // don't exclude existing sel - } - if sob != nil { - es.SelectAction(sob, e.SelectMode(), e.Pos()) - sv.UpdateSelect() - } + // release on select -- do extended selection processing + if (es.SelectNoDrag && es.Tool == SelectTool) || (es.Tool != SelectTool && ToolDoesBasicSelect(es.Tool)) { + es.SelectNoDrag = false + e.SetHandled() + if sob == nil { + sob = sv.SelectContainsPoint(e.Pos(), false, false) // don't exclude existing sel + } + if sob != nil { + es.SelectAction(sob, e.SelectMode(), e.Pos()) + sv.UpdateSelect() } } }) @@ -236,11 +234,7 @@ func (sv *SVG) Init() { se := e.(*events.MouseScroll) svoff := sv.Geom.ContentBBox.Min sv.ZoomAt(se.Pos().Sub(svoff), se.Delta.Y/100) - // sv.SVG.Scale += float32(se.Delta.Y) / 100 - // if sv.SVG.Scale <= 0.0000001 { - // sv.SVG.Scale = 0.01 - // } - go sv.RenderSVG() + sv.NeedsRender() }) } @@ -250,32 +244,6 @@ func (sv *SVG) SizeFinal() { sv.ResizeBg(sv.Geom.Size.Actual.Content.ToPoint()) } -// RenderSVG renders the SVG, typically called in a goroutine -func (sv *SVG) RenderSVG() { - if sv.SVG == nil || sv.inRender { - return - } - sv.inRender = true - defer func() { sv.inRender = false }() - - // if sv.BackgroundNeedsUpdate() { - // sv.RenderBackground() - // } - // need to make the image again to prevent it from - // rendering over itself - // sv.SVG.Background = nil - // sv.SVG.Pixels = image.NewRGBA(sv.SVG.Pixels.Rect) - // sv.SVG.RenderState.Init(sv.SVG.Pixels.Rect.Dx(), sv.SVG.Pixels.Rect.Dy(), sv.SVG.Pixels) - // sv.SVG.Render() - // sv.pixelMu.Lock() - // bgsz := sv.backgroundPixels.Bounds() - // sv.currentPixels = image.NewRGBA(bgsz) - // draw.Draw(sv.currentPixels, bgsz, sv.backgroundPixels, image.ZP, draw.Src) - // // draw.Draw(sv.currentPixels, bgsz, sv.SVG.Pixels, image.ZP, draw.Over) - // sv.NeedsRender() - // sv.pixelMu.Unlock() -} - func (sv *SVG) Render() { sv.WidgetBase.Render() if sv.SVG == nil { @@ -283,7 +251,6 @@ func (sv *SVG) Render() { } sv.SVG.SetSize(sv.Geom.Size.Actual.Content) sv.SVG.Geom.Pos = sv.Geom.Pos.Content.ToPointCeil() - fmt.Println(sv.SVG.Geom.Pos) sv.SVG.Render(&sv.Scene.Painter) // sv.pixelMu.Lock() // if sv.currentPixels == nil || sv.BackgroundNeedsUpdate() { @@ -314,7 +281,6 @@ func (sv *SVG) EditState() *EditState { func (sv *SVG) UpdateView(full bool) { // TODO(config) sv.UpdateSelSprites() sv.NeedsRender() - // go sv.RenderSVG() } /* @@ -703,12 +669,11 @@ func (sv *SVG) Redo() string { // between BBox Min - Max. typs are corresponding bounding box sources. func (sv *SVG) ShowAlignMatches(pts []image.Rectangle, typs []BBoxPoints) { sz := min(len(pts), 8) - svoff := sv.Geom.ContentBBox.Min for i := 0; i < sz; i++ { pt := pts[i].Canon() lsz := pt.Max.Sub(pt.Min) sp := Sprite(sv, SpAlignMatch, Sprites(typs[i]), i, lsz, nil) - SetSpritePos(sp, pt.Min.Add(svoff)) + SetSpritePos(sp, pt.Min) } } @@ -769,13 +734,12 @@ func NewSVGElementDrag[T tree.NodeValue](sv *SVG, start, end image.Point) *T { sn := any(n).(svg.Node) xfi := sv.Root().Paint.Transform.Inverse() svoff := math32.FromPoint(sv.Geom.ContentBBox.Min) - pos := math32.FromPoint(start).Sub(svoff) + pos := math32.FromPoint(start).Add(svoff) pos = xfi.MulVector2AsPoint(pos) sn.SetNodePos(pos) sz := dv.Abs().Max(math32.Vector2Scalar(minsz / 2)) sz = xfi.MulVector2AsVector(sz) sn.SetNodeSize(sz) - sv.RenderSVG() // needed to get bb es.SelectAction(sn, events.SelectOne, end) sv.NeedsRender() sv.UpdateSelSprites() @@ -794,7 +758,7 @@ func (sv *SVG) NewText(start, end image.Point) svg.Node { tspan.Text = "Text" tspan.Width = 200 xfi := sv.Root().Paint.Transform.Inverse() - svoff := math32.FromPoint(sv.Geom.ContentBBox.Min) + svoff := math32.Vector2{} // math32.FromPoint(sv.Geom.ContentBBox.Min) pos := math32.FromPoint(start).Sub(svoff) // minsz := float32(20) pos.Y += 20 // todo: need the font size.. diff --git a/canvas/testdata/rectgp.svg b/canvas/testdata/rectgp.svg index f293fa03..589381cb 100644 --- a/canvas/testdata/rectgp.svg +++ b/canvas/testdata/rectgp.svg @@ -8,20 +8,6 @@ xmlns="http://www.w3.org/2000/svg"> - - - + From 36411639b99deb30a78cb746bdd792beca02eb8e Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 19 May 2025 02:20:53 -0700 Subject: [PATCH 2/8] canvas: grid rendering --- canvas/svg.go | 150 +++++++++++++++----------------------------------- 1 file changed, 44 insertions(+), 106 deletions(-) diff --git a/canvas/svg.go b/canvas/svg.go index 228a4c22..b4b0810e 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -9,11 +9,11 @@ import ( "fmt" "image" "strings" - "sync" "cogentcore.org/core/base/errors" "cogentcore.org/core/base/iox/jsonx" "cogentcore.org/core/base/reflectx" + "cogentcore.org/core/colors" "cogentcore.org/core/core" "cogentcore.org/core/cursors" "cogentcore.org/core/events" @@ -21,7 +21,6 @@ import ( "cogentcore.org/core/icons" "cogentcore.org/core/keymap" "cogentcore.org/core/math32" - "cogentcore.org/core/paint" "cogentcore.org/core/styles" "cogentcore.org/core/styles/abilities" "cogentcore.org/core/svg" @@ -45,30 +44,6 @@ type SVG struct { // effective grid spacing given Scale level GridEff float32 `edit:"-" set:"-"` - // pixelMu is a mutex protecting the updating of curPixels - // from svg render - pixelMu sync.Mutex - - // currentPixels are the current rendered pixels, with the SVG - // on top of the background pixel grid. updated in separate - // goroutine, protected by pixelMu, to ensure fluid interaction - currentPixels *image.RGBA - - // background pixels, includes page outline and grid - backgroundPixels *image.RGBA - - // background paint rendering context - backgroundPaint paint.Painter - - // in svg Rendering - inRender bool - - // size of bg image rendered - backgroundSize image.Point - - // bg rendered transform - backgroundTransform math32.Matrix2 - // bg rendered grid backgroundGridEff float32 } @@ -241,7 +216,6 @@ func (sv *SVG) Init() { func (sv *SVG) SizeFinal() { sv.WidgetBase.SizeFinal() sv.SVG.SetSize(sv.Geom.Size.Actual.Content) - sv.ResizeBg(sv.Geom.Size.Actual.Content.ToPoint()) } func (sv *SVG) Render() { @@ -249,19 +223,10 @@ func (sv *SVG) Render() { if sv.SVG == nil { return } + sv.RenderGrid() sv.SVG.SetSize(sv.Geom.Size.Actual.Content) sv.SVG.Geom.Pos = sv.Geom.Pos.Content.ToPointCeil() sv.SVG.Render(&sv.Scene.Painter) - // sv.pixelMu.Lock() - // if sv.currentPixels == nil || sv.BackgroundNeedsUpdate() { - // sv.pixelMu.Unlock() - // sv.RenderSVG() - // sv.pixelMu.Lock() - // } - // r := sv.Geom.ContentBBox - // sp := sv.Geom.ScrollOffset() - // draw.Draw(sv.Scene.Pixels, r, sv.currentPixels, sp, draw.Over) - // sv.pixelMu.Unlock() } // Root returns the root [svg.Root]. @@ -584,10 +549,14 @@ func (sv *SVG) MakeNodeContextMenu(m *core.Scene, kn tree.Node) { core.NewSeparator(m) - core.NewFuncButton(m).SetFunc(sv.Canvas.DuplicateSelected).SetText("Duplicate").SetIcon(icons.Copy).SetKey(keymap.Duplicate) - core.NewFuncButton(m).SetFunc(sv.Canvas.CopySelected).SetText("Copy").SetIcon(icons.Copy).SetKey(keymap.Copy) - core.NewFuncButton(m).SetFunc(sv.Canvas.CutSelected).SetText("Cut").SetIcon(icons.Cut).SetKey(keymap.Cut) - core.NewFuncButton(m).SetFunc(sv.Canvas.PasteClip).SetText("Paste").SetIcon(icons.Paste).SetKey(keymap.Paste) + core.NewFuncButton(m).SetFunc(sv.Canvas.DuplicateSelected). + SetText("Duplicate").SetIcon(icons.Copy).SetKey(keymap.Duplicate) + core.NewFuncButton(m).SetFunc(sv.Canvas.CopySelected). + SetText("Copy").SetIcon(icons.Copy).SetKey(keymap.Copy) + core.NewFuncButton(m).SetFunc(sv.Canvas.CutSelected). + SetText("Cut").SetIcon(icons.Cut).SetKey(keymap.Cut) + core.NewFuncButton(m).SetFunc(sv.Canvas.PasteClip). + SetText("Paste").SetIcon(icons.Paste).SetKey(keymap.Paste) } // ContextMenuPos returns position to use for context menu, based on input position @@ -601,8 +570,7 @@ func (sv *SVG) NodeContextMenuPos(pos image.Point) image.Point { return pos } -/////////////////////////////////////////////////////////////////////////// -// Undo +//////// Undo // UndoSave save current state for potential undo func (sv *SVG) UndoSave(action, data string) { @@ -662,8 +630,7 @@ func (sv *SVG) Redo() string { return act } -/////////////////////////////////////////////////////////////////// -// selection processing +//////// selection processing // ShowAlignMatches draws the align matches as given // between BBox Min - Max. typs are corresponding bounding box sources. @@ -691,8 +658,7 @@ func (sv *SVG) DepthMap() map[tree.Node]int { return m } -/////////////////////////////////////////////////////////////////////// -// New objects +//////// New objects // SetSVGName sets the name of the element to standard type + id name func (sv *SVG) SetSVGName(el svg.Node) { @@ -812,8 +778,7 @@ func (sv *SVG) NewPath(start, end image.Point) *svg.Path { return n } -/////////////////////////////////////////////////////////////////////// -// Gradients +/////// Gradients // Gradients returns the currently defined gradients with stops // that are shared among obj-specific ones @@ -863,22 +828,7 @@ func (sv *SVG) UpdateGradients(gl []*Gradient) { // sv.UpdateAllGradientStops() } -/////////////////////////////////////////////////////////////////////// -// Bg render - -func (sv *SVG) BackgroundNeedsUpdate() bool { - root := sv.Root() - if root == nil { - return false - } - return sv.backgroundPixels == nil || sv.backgroundPixels.Bounds().Size() != sv.backgroundSize || sv.backgroundTransform != root.Paint.Transform || sv.GridEff != sv.backgroundGridEff || sv.NeedsRebuild() -} - -func (sv *SVG) ResizeBg(sz image.Point) { - if sv.backgroundPaint.State == nil { - sv.backgroundPaint = *paint.NewPainter(math32.FromPoint(sz)) - } -} +//////// Bg render // UpdateGridEff updates the GirdEff value based on current scale func (sv *SVG) UpdateGridEff() { @@ -890,49 +840,37 @@ func (sv *SVG) UpdateGridEff() { } } -// RenderBackground renders our background grid image -func (sv *SVG) RenderBackground() { +// RenderGrid renders the background grid +func (sv *SVG) RenderGrid() { root := sv.Root() if root == nil { return } - // sv.UpdateGridEff() - // bb := sv.backgroundPixels.Bounds() - // draw.Draw(sv.backgroundPixels, bb, colors.Scheme.Surface, image.ZP, draw.Src) - // - // pc := &sv.backgroundPaint - // pc.PushBounds(bb) - // pc.PushTransform(root.Paint.Transform) - // - // pc.StrokeStyle.Color = colors.Scheme.Outline - // - // sc := sv.SVG.Scale - // - // wd := 1 / sc - // pc.StrokeStyle.Width.Dots = wd - // pos := math32.Vec2(0, 0) - // sz := root.ViewBox.Size - // pc.FillStyle.Color = nil - // - // pc.DrawRectangle(pos.X, pos.Y, sz.X, sz.Y) - // pc.FillStrokeClear() - // - // if Settings.GridDisp { - // gsz := float32(sv.GridEff) - // pc.StrokeStyle.Color = colors.Scheme.OutlineVariant - // for x := gsz; x < sz.X; x += gsz { - // pc.DrawLine(x, 0, x, sz.Y) - // } - // for y := gsz; y < sz.Y; y += gsz { - // pc.DrawLine(0, y, sz.X, y) - // } - // pc.FillStrokeClear() - // } - // - // sv.backgroundTransform = root.Paint.Transform - // sv.backgroundGridEff = sv.GridEff - // sv.backgroundSize = bb.Size() - // - // pc.PopTransform() - // pc.PopBounds() + sv.UpdateGridEff() + + pc := &sv.Scene.Painter + pc.PushContext(&root.Paint, nil) + pc.Stroke.Color = colors.Scheme.Outline + pc.Fill.Color = nil + + sc := sv.SVG.Scale + + wd := 1 / sc + pc.Stroke.Width.Dots = wd + pos := math32.Vec2(0, 0) + sz := root.ViewBox.Size + + pc.Rectangle(pos.X, pos.Y, sz.X, sz.Y) + if Settings.GridDisp { + gsz := float32(sv.GridEff) + pc.Stroke.Color = colors.Scheme.OutlineVariant + for x := gsz; x < sz.X; x += gsz { + pc.Line(x, 0, x, sz.Y) + } + for y := gsz; y < sz.Y; y += gsz { + pc.Line(0, y, sz.X, y) + } + } + pc.Draw() + pc.PopContext() } From 72f1da8656af66788ff184069d3d5a0604af5735 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 19 May 2025 13:23:14 -0700 Subject: [PATCH 3/8] canvas: sprites logic updated to new batch-mode; new drag items working --- canvas/canvas.go | 1 + canvas/edits.go | 17 +++++ canvas/manip.go | 31 +++++++-- canvas/path.go | 13 ++-- canvas/select.go | 42 +++++++----- canvas/shapes.go | 142 +++++++++++++++++++++++++++++++++++++++ canvas/sprites.go | 24 ++++--- canvas/svg.go | 166 +++++----------------------------------------- 8 files changed, 247 insertions(+), 189 deletions(-) create mode 100644 canvas/shapes.go diff --git a/canvas/canvas.go b/canvas/canvas.go index 97ff3697..88840df3 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -47,6 +47,7 @@ func (cv *Canvas) Init() { }) cv.AddCloseDialog(func(d *core.Body) bool { + return false // todo: temporary disable -- need to fix bug if !cv.EditState.Changed { return false } diff --git a/canvas/edits.go b/canvas/edits.go index 9b48d74f..0ec34aeb 100644 --- a/canvas/edits.go +++ b/canvas/edits.go @@ -5,6 +5,7 @@ package canvas import ( + "fmt" "image" "image/color" "sort" @@ -15,6 +16,7 @@ import ( "cogentcore.org/core/events" "cogentcore.org/core/math32" "cogentcore.org/core/svg" + "cogentcore.org/core/tree" "cogentcore.org/core/undo" ) @@ -194,6 +196,20 @@ func (es *EditState) SelectedList(descendingSort bool) []svg.Node { return sls } +// DepthMap returns a map of all nodes and their associated depth count +// counting up from 0 as the deepest, first drawn node. +func (sv *SVG) DepthMap() map[tree.Node]int { + m := make(map[tree.Node]int) + depth := 0 + n := tree.Next(sv.This) + for n != nil { + m[n] = depth + depth++ + n = tree.Next(n) + } + return m +} + // SelectedListDepth returns list of selected items, sorted either // ascending or descending according to depth: // ascending = deepest first, descending = highest first @@ -415,6 +431,7 @@ func (es *EditState) DragSelStart(pos image.Point) { // position is starting position. func (es *EditState) DragNodeStart(pos image.Point) { es.DragStartPos = pos + fmt.Println("dragNodestart:", pos) } ////////////////////////////////////////////////////// diff --git a/canvas/manip.go b/canvas/manip.go index bfa91e50..d065912b 100644 --- a/canvas/manip.go +++ b/canvas/manip.go @@ -27,13 +27,14 @@ func (sv *SVG) ManipStart(act Actions, data string) { // ManipDone happens when a manipulation has finished: resets action, does render func (sv *SVG) ManipDone() { - InactivateSprites(sv, SpAlignMatch) + sprites := sv.SpritesLock() + InactivateSprites(sprites, SpAlignMatch) es := sv.EditState() switch { case es.Action == BoxSelect: bbox := image.Rectangle{Min: es.DragStartPos, Max: es.DragCurPos} bbox = bbox.Canon() - InactivateSprites(sv, SpRubberBand) + InactivateSprites(sprites, SpRubberBand) fmt.Println(bbox) sel := sv.SelectWithinBBox(bbox, false) if len(sel) > 0 { @@ -45,6 +46,7 @@ func (sv *SVG) ManipDone() { default: } es.DragReset() + sprites.Unlock() es.ActDone() sv.UpdateSelect() es.DragSelStart(es.DragStartPos) // capture final state as new start @@ -292,11 +294,26 @@ func (sv *SVG) ConstrainPoint(st, rawpt math32.Vector2) (math32.Vector2, bool) { return cp, diag } +// ShowAlignMatches draws the align matches as given +// between BBox Min - Max. typs are corresponding bounding box sources. +// sprites must already be locked. +func (sv *SVG) ShowAlignMatches(pts []image.Rectangle, typs []BBoxPoints) { + sprites := sv.SpritesNolock() + sz := min(len(pts), 8) + for i := 0; i < sz; i++ { + pt := pts[i].Canon() + lsz := pt.Max.Sub(pt.Min) + sp := Sprite(sprites, SpAlignMatch, Sprites(typs[i]), i, lsz, nil) + SetSpritePos(sp, pt.Min) + } +} + // DragMove is when dragging a selection for moving func (sv *SVG) DragMove(e events.Event) { es := sv.EditState() + sprites := sv.SpritesLock() - InactivateSprites(sv, SpAlignMatch) + InactivateSprites(sprites, SpAlignMatch) if !es.InAction() { sv.ManipStart(Move, es.SelectedNamesString()) @@ -327,7 +344,7 @@ func (sv *SVG) DragMove(e events.Event) { itm.ApplyDeltaTransform(sv.SVG, tdel, math32.Vec2(1, 1), 0, pt) } sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.DragSelectEffectiveBBox) - sv.SetSelSpritePos() + sprites.Unlock() sv.NeedsRender() } @@ -361,7 +378,8 @@ func ProportionalBBox(bb, orig math32.Box2) math32.Box2 { func (sv *SVG) SpriteReshapeDrag(sp Sprites, e events.Event) { es := sv.EditState() - InactivateSprites(sv, SpAlignMatch) + sprites := sv.SpritesLock() + InactivateSprites(sprites, SpAlignMatch) if !es.InAction() { sv.ManipStart(Reshape, es.SelectedNamesString()) @@ -446,7 +464,7 @@ func (sv *SVG) SpriteReshapeDrag(sp Sprites, e events.Event) { } sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.DragSelectEffectiveBBox) - sv.SetSelSpritePos() + sprites.Unlock() sv.NeedsRender() } @@ -523,6 +541,5 @@ func (sv *SVG) SpriteRotateDrag(sp Sprites, delta image.Point) { } sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.DragSelectCurrentBBox) - sv.SetSelSpritePos() sv.NeedsRender() } diff --git a/canvas/path.go b/canvas/path.go index eae80a8c..05fd3b3a 100644 --- a/canvas/path.go +++ b/canvas/path.go @@ -166,38 +166,41 @@ func (sv *SVG) UpdateNodeSprites() { return } + sprites := sv.SpritesLock() + es.PathNodes, es.PathCommands = sv.PathNodes(path) es.NNodeSprites = len(es.PathNodes) es.ActivePath = path for i, pn := range es.PathNodes { - sp := Sprite(sv, SpNodePoint, SpUnk, i, image.Point{}, func(sp *core.Sprite) { + sp := Sprite(sprites, SpNodePoint, SpUnk, i, image.Point{}, func(sp *core.Sprite) { // todo: events here }) SetSpritePos(sp, pn.WinPt.ToPoint()) } // remove extra - sprites := &sv.Scene.Stage.Sprites for i := es.NNodeSprites; i < prvn; i++ { spnm := SpriteName(SpNodePoint, SpUnk, i) - sprites.InactivateSprite(spnm) + sprites.InactivateSpriteLocked(spnm) } + sprites.Unlock() sv.Canvas.UpdateNodeToolbar() } func (sv *SVG) RemoveNodeSprites() { es := sv.EditState() - sprites := &sv.Scene.Stage.Sprites + sprites := sv.SpritesLock() for i := 0; i < es.NNodeSprites; i++ { spnm := SpriteName(SpNodePoint, SpUnk, i) - sprites.InactivateSprite(spnm) + sprites.InactivateSpriteLocked(spnm) } es.NNodeSprites = 0 es.PathNodes = nil es.PathCommands = nil es.ActivePath = nil + sprites.Unlock() } /* diff --git a/canvas/select.go b/canvas/select.go index 54a74bfa..0f5cdb5d 100644 --- a/canvas/select.go +++ b/canvas/select.go @@ -144,23 +144,28 @@ func (sv *SVG) UpdateSelect() { } func (sv *SVG) RemoveSelSprites() { - InactivateSprites(sv, SpReshapeBBox) - InactivateSprites(sv, SpSelBBox) + sv.NeedsRender() + sprites := sv.SpritesLock() + InactivateSprites(sprites, SpReshapeBBox) + InactivateSprites(sprites, SpSelBBox) es := sv.EditState() es.NSelectSprites = 0 + sprites.Unlock() } func (sv *SVG) UpdateSelSprites() { + sv.NeedsRender() es := sv.EditState() es.UpdateSelectBBox() if !es.HasSelected() { sv.RemoveSelSprites() return } - + sprites := sv.SpritesLock() for i := SpBBoxUpL; i <= SpBBoxRtM; i++ { - Sprite(sv, SpReshapeBBox, i, 0, image.Point{}, func(sp *core.Sprite) { + Sprite(sprites, SpReshapeBBox, i, 0, image.Point{}, func(sp *core.Sprite) { sp.OnSlideStart(func(e events.Event) { + fmt.Println("node sel start") es.DragSelStart(e.Pos()) e.SetHandled() }) @@ -179,10 +184,12 @@ func (sv *SVG) UpdateSelSprites() { }) } sv.SetBBoxSpritePos(SpReshapeBBox, 0, es.SelectBBox) - sv.SetSelSpritePos() + sprites.Unlock() } -func (sv *SVG) SetSelSpritePos() { +// setSelSpritePos sets the selection sprites positions. +// only called by SetBBoxSpritePos. +func (sv *SVG) setSelSpritePos() { es := sv.EditState() nsel := es.NSelectSprites @@ -201,24 +208,26 @@ func (sv *SVG) SetSelSpritePos() { nbox++ } es.NSelectSprites = nbox + return } - sprites := &sv.Scene.Stage.Sprites + sprites := sv.SpritesNolock() for si := es.NSelectSprites; si < nsel; si++ { for i := SpBBoxUpL; i <= SpBBoxRtM; i++ { spnm := SpriteName(SpSelBBox, i, si) - sprites.InactivateSprite(spnm) + sprites.InactivateSpriteLocked(spnm) } } } -// SetBBoxSpritePos sets positions of given type of sprites +// SetBBoxSpritePos sets positions of given type of sprites. func (sv *SVG) SetBBoxSpritePos(typ Sprites, idx int, bbox math32.Box2) { + sprites := sv.SpritesNolock() _, spsz := HandleSpriteSize(1) midX := int(0.5 * (bbox.Min.X + bbox.Max.X - float32(spsz.X))) midY := int(0.5 * (bbox.Min.Y + bbox.Max.Y - float32(spsz.Y))) for i := SpBBoxUpL; i <= SpBBoxRtM; i++ { - sp := Sprite(sv, typ, i, idx, image.ZP, nil) + sp := Sprite(sprites, typ, i, idx, image.ZP, nil) switch i { case SpBBoxUpL: SetSpritePos(sp, image.Point{int(bbox.Min.X), int(bbox.Min.Y)}) @@ -273,6 +282,7 @@ func (sv *SVG) SelSpriteEvent(sp Sprites, et events.EventType, d any) { // SetRubberBand updates the rubber band position. func (sv *SVG) SetRubberBand(cur image.Point) { es := sv.EditState() + sprites := sv.SpritesLock() if !es.InAction() { es.ActStart(BoxSelect, fmt.Sprintf("%v", es.DragStartPos)) @@ -290,15 +300,16 @@ func (sv *SVG) SetRubberBand(cur image.Point) { if sz.Y < 4 { sz.Y = 4 } - rt := Sprite(sv, SpRubberBand, SpBBoxUpC, 0, sz, nil) - rb := Sprite(sv, SpRubberBand, SpBBoxDnC, 0, sz, nil) - rr := Sprite(sv, SpRubberBand, SpBBoxRtM, 0, sz, nil) - rl := Sprite(sv, SpRubberBand, SpBBoxLfM, 0, sz, nil) + rt := Sprite(sprites, SpRubberBand, SpBBoxUpC, 0, sz, nil) + rb := Sprite(sprites, SpRubberBand, SpBBoxDnC, 0, sz, nil) + rr := Sprite(sprites, SpRubberBand, SpBBoxRtM, 0, sz, nil) + rl := Sprite(sprites, SpRubberBand, SpBBoxLfM, 0, sz, nil) SetSpritePos(rt, bbox.Min) SetSpritePos(rb, image.Point{bbox.Min.X, bbox.Max.Y}) SetSpritePos(rr, image.Point{bbox.Max.X, bbox.Min.Y}) SetSpritePos(rl, bbox.Min) + sprites.Unlock() sv.NeedsRender() } @@ -571,8 +582,7 @@ func (gv *Canvas) SelectSetHeight(ht float32) { gv.ChangeMade() } -/////////////////////////////////////////////////////////////////////// -// Select tree traversal +//////// Select tree traversal // SelectWithinBBox returns a list of all nodes whose BBox is fully contained // within the given BBox. SVG version excludes layer groups. diff --git a/canvas/shapes.go b/canvas/shapes.go new file mode 100644 index 00000000..f948e2c1 --- /dev/null +++ b/canvas/shapes.go @@ -0,0 +1,142 @@ +// Copyright (c) 2021, Cogent Core. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package canvas + +import ( + "fmt" + "image" + + "cogentcore.org/core/events" + "cogentcore.org/core/math32" + "cogentcore.org/core/svg" + "cogentcore.org/core/tree" + "cogentcore.org/core/types" +) + +// SetSVGName sets the name of the element to standard type + id name +func (sv *SVG) SetSVGName(el svg.Node) { + nwid := sv.SVG.NewUniqueID() + nwnm := fmt.Sprintf("%s%d", el.SVGName(), nwid) + el.AsTree().SetName(nwnm) +} + +// NewSVGElement makes a new SVG element of the given type. +// It uses the current active layer if it is set. +func NewSVGElement[T tree.NodeValue](sv *SVG) *T { + es := sv.EditState() + parent := tree.Node(sv.Root()) + if es.CurLayer != "" { + ly := sv.ChildByName(es.CurLayer, 1) + if ly != nil { + parent = ly + } + } + n := tree.New[T](parent) + sn := any(n).(svg.Node) + sv.SetSVGName(sn) + sv.Canvas.PaintView().SetProperties(sn) + sv.Canvas.UpdateTree() + return n +} + +// NewSVGElementDrag makes a new SVG element of the given type during the drag operation. +func NewSVGElementDrag[T tree.NodeValue](sv *SVG, start, end image.Point) *T { + minsz := float32(10) + es := sv.EditState() + dv := math32.FromPoint(end.Sub(start)) + if !es.InAction() && math32.Abs(dv.X) < minsz && math32.Abs(dv.Y) < minsz { + // fmt.Println("dv under min:", dv, minsz) + return nil + } + sv.ManipStart(NewElement, types.For[T]().IDName) + n := NewSVGElement[T](sv) + sn := any(n).(svg.Node) + snb := sn.AsNodeBase() + + xfi := sv.Root().Paint.Transform.Inverse() + pos := xfi.MulVector2AsPoint(math32.FromPoint(start)) + sn.SetNodePos(pos) + sz := dv.Abs().Max(math32.Vector2Scalar(minsz / 2)) + sz = xfi.MulVector2AsVector(sz) + sn.SetNodeSize(sz) + sn.BBoxes(sv.SVG, snb.ParentTransform(false)) + + es.SelectAction(sn, events.SelectOne, end) + sv.UpdateSelSprites() + es.DragSelStart(start) + sv.NeedsRender() + return n +} + +// NewText makes a new Text element with embedded tspan +func (sv *SVG) NewText(start, end image.Point) svg.Node { + minsz := float32(10) + es := sv.EditState() + dv := math32.FromPoint(end.Sub(start)) + if !es.InAction() && math32.Abs(dv.X) < minsz && math32.Abs(dv.Y) < minsz { + // fmt.Println("dv under min:", dv, minsz) + return nil + } + + sv.ManipStart(NewText, "") + n := NewSVGElement[svg.Text](sv) + tsnm := fmt.Sprintf("tspan%d", sv.SVG.NewUniqueID()) + tspan := svg.NewText(n) + tspan.SetName(tsnm) + tspan.Text = "Text" + tspan.Width = 200 + + xfi := sv.Root().Paint.Transform.Inverse() + pos := math32.FromPoint(start) + // minsz := float32(20) + pos.Y += 20 // todo: need the font size.. + pos = xfi.MulVector2AsPoint(pos) + // sv.Canvas.SetTextPropertiesNode(n, es.Text.TextProperties()) + tspan.Pos = pos + sz := dv.Abs().Max(math32.Vector2Scalar(minsz / 2)) + sz = xfi.MulVector2AsVector(sz) + tspan.SetNodeSize(sz) + tspan.BBoxes(sv.SVG, tspan.ParentTransform(false)) + + es.SelectAction(tspan, events.SelectOne, end) + sv.UpdateSelSprites() + es.DragSelStart(start) + sv.NeedsRender() + return n +} + +// NewPath makes a new SVG Path element during the drag operation +func (sv *SVG) NewPath(start, end image.Point) *svg.Path { + minsz := float32(10) + es := sv.EditState() + dv := math32.FromPoint(end.Sub(start)) + if !es.InAction() && math32.Abs(dv.X) < minsz && math32.Abs(dv.Y) < minsz { + return nil + } + sv.ManipStart(NewPath, "") + n := NewSVGElement[svg.Path](sv) + xfi := sv.Root().Paint.Transform.Inverse() + pos := xfi.MulVector2AsPoint(math32.FromPoint(start)) + sz := xfi.MulVector2AsVector(dv) + fmt.Println(n.Data) + n.Data = nil + n.Data.MoveTo(pos.X, pos.Y) + n.Data.LineTo(sz.X, sz.Y) + n.BBoxes(sv.SVG, n.ParentTransform(false)) + + es.SelectAction(n, events.SelectOne, end) + sv.UpdateSelSprites() + sv.EditState().DragSelStart(start) + sv.NeedsRender() + + es.SelectBBox.Min.X += 1 + es.SelectBBox.Min.Y += 1 + es.DragSelectStartBBox = es.SelectBBox + es.DragSelectCurrentBBox = es.SelectBBox + es.DragSelectEffectiveBBox = es.SelectBBox + + // win.SpriteDragging = SpriteName(SpReshapeBBox, SpBBoxDnR, 0) + return n +} diff --git a/canvas/sprites.go b/canvas/sprites.go index a75bc9ec..1061a631 100644 --- a/canvas/sprites.go +++ b/canvas/sprites.go @@ -15,6 +15,9 @@ import ( "cogentcore.org/core/math32" ) +// note: all sprite functions assume overall sprites are locked. +// which keeps everything consistent under async rendering. + // Sprites are the type of sprite type Sprites int32 //enums:enum @@ -135,15 +138,14 @@ func SpriteByName(ctx core.Widget, typ, subtyp Sprites, idx int) (*core.Sprite, // Sprite returns the given sprite in the context of the given widget, // making it if not yet made. trgsz is the target size (e.g., for rubber // band boxes). Init function is called on new sprites. -func Sprite(ctx core.Widget, typ, subtyp Sprites, idx int, trgsz image.Point, init func(sp *core.Sprite)) *core.Sprite { - sprites := &ctx.AsWidget().Scene.Stage.Sprites +func Sprite(sprites *core.Sprites, typ, subtyp Sprites, idx int, trgsz image.Point, init func(sp *core.Sprite)) *core.Sprite { spnm := SpriteName(typ, subtyp, idx) - sp, ok := sprites.SpriteByName(spnm) + sp, ok := sprites.SpriteByNameLocked(spnm) if !ok { sp = core.NewSprite(spnm, image.Point{}, image.Point{}) sp.Properties = map[string]any{} SetSpriteProperties(sp, typ, subtyp, idx) - sprites.Add(sp) + sprites.AddLocked(sp) switch typ { case SpReshapeBBox: DrawSpriteReshape(sp, subtyp) @@ -174,7 +176,7 @@ func Sprite(ctx core.Widget, typ, subtyp Sprites, idx int, trgsz image.Point, in DrawAlignMatchVert(sp, trgsz) } } - sprites.ActivateSprite(sp.Name) + sprites.ActivateSpriteLocked(sp.Name) return sp } @@ -223,20 +225,20 @@ func SetSpritePos(sp *core.Sprite, pos image.Point) { sp.Geom.Pos = pos } -// InactivateSprites inactivates sprites of given type -func InactivateSprites(ctx core.Widget, typ Sprites) { - sprites := &ctx.AsWidget().Scene.Stage.Sprites +// InactivateSprites inactivates sprites of given type; must be locked. +func InactivateSprites(sprites *core.Sprites, typ Sprites) { + nms := []string{} for _, spkv := range sprites.Order { sp := spkv.Value st, _, _ := SpriteProperties(sp) if st == typ { - sprites.InactivateSprite(sp.Name) + nms = append(nms, sp.Name) } } + sprites.InactivateSpriteLocked(nms...) } -/////////////////////////////////////////////////////////////////// -// Sprite rendering +//////// Sprite rendering var ( HandleSpriteScale = float32(12) diff --git a/canvas/svg.go b/canvas/svg.go index b4b0810e..3ade52ee 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -25,7 +25,6 @@ import ( "cogentcore.org/core/styles/abilities" "cogentcore.org/core/svg" "cogentcore.org/core/tree" - "cogentcore.org/core/types" ) // SVG is the element for viewing and interacting with the SVG. @@ -58,6 +57,7 @@ func (sv *SVG) Init() { s.ObjectFit = styles.FitNone sv.SVG.Root.ViewBox.PreserveAspectRatio.SetFromStyle(s) s.Cursor = cursors.Arrow // todo: modulate based on tool etc + sv.SVG.TextShaper = sv.Scene.TextShaper() }) sv.OnKeyChord(func(e events.Event) { kc := e.KeyChord() @@ -119,6 +119,7 @@ func (sv *SVG) Init() { sv.UpdateNodeSprites() case sob == nil: es.DragStartPos = e.Pos() + // fmt.Println("Drag start:", es.DragStartPos) // todo: not nec es.ResetSelected() sv.UpdateSelect() } @@ -146,7 +147,8 @@ func (sv *SVG) Init() { es := sv.EditState() es.SelectNoDrag = false e.SetHandled() - es.DragStartPos = e.StartPos() + es.DragStartPos = e.StartPos() // this is the operative start + // fmt.Println("sm drag start:", es.DragStartPos) if e.HasAnyModifier(key.Shift) { del := e.PrevDelta() sv.SVG.Translate.X += float32(del.X) @@ -248,6 +250,18 @@ func (sv *SVG) UpdateView(full bool) { // TODO(config) sv.NeedsRender() } +// SpritesNolock returns the [core.Sprites] without locking. +func (sv *SVG) SpritesNolock() *core.Sprites { + return &sv.Scene.Stage.Sprites +} + +// SpritesLock returns the [core.Sprites] under mutex lock. +func (sv *SVG) SpritesLock() *core.Sprites { + sprites := sv.SpritesNolock() + sprites.Lock() + return sprites +} + /* func (sv *SVG) MouseHover() { sv.ConnectEvent(oswin.MouseHoverEvent, core.RegPri, func(recv, send tree.Node, sig int64, d any) { @@ -630,154 +644,6 @@ func (sv *SVG) Redo() string { return act } -//////// selection processing - -// ShowAlignMatches draws the align matches as given -// between BBox Min - Max. typs are corresponding bounding box sources. -func (sv *SVG) ShowAlignMatches(pts []image.Rectangle, typs []BBoxPoints) { - sz := min(len(pts), 8) - for i := 0; i < sz; i++ { - pt := pts[i].Canon() - lsz := pt.Max.Sub(pt.Min) - sp := Sprite(sv, SpAlignMatch, Sprites(typs[i]), i, lsz, nil) - SetSpritePos(sp, pt.Min) - } -} - -// DepthMap returns a map of all nodes and their associated depth count -// counting up from 0 as the deepest, first drawn node. -func (sv *SVG) DepthMap() map[tree.Node]int { - m := make(map[tree.Node]int) - depth := 0 - n := tree.Next(sv.This) - for n != nil { - m[n] = depth - depth++ - n = tree.Next(n) - } - return m -} - -//////// New objects - -// SetSVGName sets the name of the element to standard type + id name -func (sv *SVG) SetSVGName(el svg.Node) { - nwid := sv.SVG.NewUniqueID() - nwnm := fmt.Sprintf("%s%d", el.SVGName(), nwid) - el.AsTree().SetName(nwnm) -} - -// NewSVGElement makes a new SVG element of the given type. -// It uses the current active layer if it is set. -func NewSVGElement[T tree.NodeValue](sv *SVG) *T { - es := sv.EditState() - parent := tree.Node(sv.Root()) - if es.CurLayer != "" { - ly := sv.ChildByName(es.CurLayer, 1) - if ly != nil { - parent = ly - } - } - n := tree.New[T](parent) - sn := any(n).(svg.Node) - sv.SetSVGName(sn) - sv.Canvas.PaintView().SetProperties(sn) - sv.Canvas.UpdateTree() - return n -} - -// NewSVGElementDrag makes a new SVG element of the given type during the drag operation. -func NewSVGElementDrag[T tree.NodeValue](sv *SVG, start, end image.Point) *T { - minsz := float32(10) - es := sv.EditState() - dv := math32.FromPoint(end.Sub(start)) - if !es.InAction() && math32.Abs(dv.X) < minsz && math32.Abs(dv.Y) < minsz { - // fmt.Println("dv under min:", dv, minsz) - return nil - } - sv.ManipStart(NewElement, types.For[T]().IDName) - n := NewSVGElement[T](sv) - sn := any(n).(svg.Node) - xfi := sv.Root().Paint.Transform.Inverse() - svoff := math32.FromPoint(sv.Geom.ContentBBox.Min) - pos := math32.FromPoint(start).Add(svoff) - pos = xfi.MulVector2AsPoint(pos) - sn.SetNodePos(pos) - sz := dv.Abs().Max(math32.Vector2Scalar(minsz / 2)) - sz = xfi.MulVector2AsVector(sz) - sn.SetNodeSize(sz) - es.SelectAction(sn, events.SelectOne, end) - sv.NeedsRender() - sv.UpdateSelSprites() - es.DragSelStart(start) - return n -} - -// NewText makes a new Text element with embedded tspan -func (sv *SVG) NewText(start, end image.Point) svg.Node { - es := sv.EditState() - sv.ManipStart(NewText, "") - n := NewSVGElement[svg.Text](sv) - tsnm := fmt.Sprintf("tspan%d", sv.SVG.NewUniqueID()) - tspan := svg.NewText(n) - tspan.SetName(tsnm) - tspan.Text = "Text" - tspan.Width = 200 - xfi := sv.Root().Paint.Transform.Inverse() - svoff := math32.Vector2{} // math32.FromPoint(sv.Geom.ContentBBox.Min) - pos := math32.FromPoint(start).Sub(svoff) - // minsz := float32(20) - pos.Y += 20 // todo: need the font size.. - pos = xfi.MulVector2AsPoint(pos) - // sv.Canvas.SetTextPropertiesNode(n, es.Text.TextProperties()) - // nr.Pos = pos - // tspan.Pos = pos - // // dv := math32.FromPoint(end.Sub(start)) - // // sz := dv.Abs().Max(math32.NewVector2Scalar(minsz / 2)) - // nr.Width = 100 - // tspan.Width = 100 - es.SelectAction(n, events.SelectOne, end) - // sv.UpdateView(true) - // sv.UpdateSelect() - return n -} - -// NewPath makes a new SVG Path element during the drag operation -func (sv *SVG) NewPath(start, end image.Point) *svg.Path { - minsz := float32(10) - es := sv.EditState() - dv := math32.FromPoint(end.Sub(start)) - if !es.InAction() && math32.Abs(dv.X) < minsz && math32.Abs(dv.Y) < minsz { - return nil - } - // win := sv.Vector.ParentWindow() - sv.ManipStart(NewPath, "") - // sv.SetFullReRender() - n := NewSVGElement[svg.Path](sv) - xfi := sv.Root().Paint.Transform.Inverse() - // svoff := math32.FromPoint(sv.Geom.ContentBBox.Min) - pos := math32.FromPoint(start) - pos = xfi.MulVector2AsPoint(pos) - sz := dv - // sz := dv.Abs().Max(math32.NewVector2Scalar(minsz / 2)) - sz = xfi.MulVector2AsVector(sz) - - n.SetData(fmt.Sprintf("m %g,%g %g,%g", pos.X, pos.Y, sz.X, sz.Y)) - - es.SelectAction(n, events.SelectOne, end) - sv.UpdateSelSprites() - sv.EditState().DragSelStart(start) - - es.SelectBBox.Min.X += 1 - es.SelectBBox.Min.Y += 1 - es.DragSelectStartBBox = es.SelectBBox - es.DragSelectCurrentBBox = es.SelectBBox - es.DragSelectEffectiveBBox = es.SelectBBox - - // win.SpriteDragging = SpriteName(SpReshapeBBox, SpBBoxDnR, 0) - return n -} - /////// Gradients // Gradients returns the currently defined gradients with stops From 52699a3330adde8e2ca0c433ce2f1694cee57486 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 19 May 2025 17:42:45 -0700 Subject: [PATCH 4/8] canvas: updated to math32.Vector2 bboxes in svg, all the basic zoom, resize, grid etc logic is pretty solid now (still some issues in ZoomAt with smaller images / with viewbox offsets) --- canvas/align.go | 172 ++++++++++++-------------------- canvas/bbox.go | 36 ------- canvas/canvas.go | 254 +++++++++++++++++++++-------------------------- canvas/edits.go | 3 +- canvas/layers.go | 52 +++++----- canvas/manip.go | 3 +- canvas/paint.go | 20 ++-- canvas/path.go | 4 +- canvas/select.go | 47 ++++----- canvas/svg.go | 128 +++++++----------------- canvas/text.go | 4 +- canvas/tools.go | 43 ++++---- canvas/tree.go | 100 +++++++++---------- 13 files changed, 348 insertions(+), 518 deletions(-) diff --git a/canvas/align.go b/canvas/align.go index ff6ba9ef..1210dc80 100644 --- a/canvas/align.go +++ b/canvas/align.go @@ -5,8 +5,6 @@ package canvas import ( - "image" - "cogentcore.org/cogent/canvas/cicons" "cogentcore.org/core/core" "cogentcore.org/core/events" @@ -66,55 +64,56 @@ func (av *AlignView) Init() { tree.AddChildAt(w, al.String(), func(w *core.Button) { w.SetIcon(AlignIcons[al]).SetType(core.ButtonTonal).SetTooltip(al.Desc()) w.OnClick(func(e events.Event) { - av.Canvas.Align(av.Anchor, al) + av.Canvas.SVG.Align(av.Anchor, al) }) }) } }) } -///////////////////////////////////////////////////////////////////////// -// Actions +//////// Actions -func (vv *Canvas) Align(aa AlignAnchors, al Aligns) { +func (sv *SVG) Align(aa AlignAnchors, al Aligns) { astr := al.String() switch al { case AlignRightAnchor: - vv.AlignMaxAnchor(aa, math32.X, astr) + sv.AlignMaxAnchor(aa, math32.X, astr) case AlignLeft: - vv.AlignMin(aa, math32.X, astr) + sv.AlignMin(aa, math32.X, astr) case AlignCenter: - vv.AlignCenter(aa, math32.X, astr) + sv.AlignCenter(aa, math32.X, astr) case AlignRight: - vv.AlignMax(aa, math32.X, astr) + sv.AlignMax(aa, math32.X, astr) case AlignLeftAnchor: - vv.AlignMinAnchor(aa, math32.X, astr) + sv.AlignMinAnchor(aa, math32.X, astr) case AlignBaselineHoriz: - vv.AlignMin(aa, math32.X, astr) // todo: should be baseline, not min + sv.AlignMin(aa, math32.X, astr) // todo: should be baseline, not min case AlignBottomAnchor: - vv.AlignMaxAnchor(aa, math32.Y, astr) + sv.AlignMaxAnchor(aa, math32.Y, astr) case AlignTop: - vv.AlignMin(aa, math32.Y, astr) + sv.AlignMin(aa, math32.Y, astr) case AlignMiddle: - vv.AlignCenter(aa, math32.Y, astr) + sv.AlignCenter(aa, math32.Y, astr) case AlignBottom: - vv.AlignMax(aa, math32.Y, astr) + sv.AlignMax(aa, math32.Y, astr) case AlignTopAnchor: - vv.AlignMinAnchor(aa, math32.Y, astr) + sv.AlignMinAnchor(aa, math32.Y, astr) case AlignBaselineVert: - vv.AlignMin(aa, math32.Y, astr) // todo: should be baseline, not min + sv.AlignMin(aa, math32.Y, astr) // todo: should be baseline, not min } + sv.UpdateView(true) + sv.Canvas.ChangeMade() + sv.UpdateSelSprites() } -// AlignAnchorBBox returns the bounding box for given type of align anchor +// alignAnchorBBox returns the bounding box for given type of align anchor // and the anchor node if non-nil -func (vv *Canvas) AlignAnchorBBox(aa AlignAnchors) (image.Rectangle, svg.Node) { - es := &vv.EditState - sv := vv.SVG() - svoff := sv.Root().BBox.Min +func (sv *SVG) alignAnchorBBox(aa AlignAnchors) (math32.Box2, svg.Node) { + es := sv.EditState() + // svoff := sv.Root().BBox.Min var an svg.Node - var bb image.Rectangle + var bb math32.Box2 switch aa { case AlignFirst: sl := es.SelectedList(false) @@ -125,22 +124,20 @@ func (vv *Canvas) AlignAnchorBBox(aa AlignAnchors) (image.Rectangle, svg.Node) { an = sl[0] bb = an.AsNodeBase().BBox case AlignSelectBox: - bb = image.Rectangle{Min: es.DragSelectCurrentBBox.Min.ToPointFloor(), Max: es.DragSelectCurrentBBox.Max.ToPointCeil()} + bb = es.DragSelectCurrentBBox } - bb = bb.Sub(svoff) + // bb = bb.Sub(svoff) return bb, an } -// AlignMin aligns to min coordinate (Left, Top) in bbox -func (vv *Canvas) AlignMin(aa AlignAnchors, dim math32.Dims, act string) { - es := &vv.EditState +// alignImpl does alignment +func (sv *SVG) alignImpl(apos math32.Vector2, an svg.Node, dim math32.Dims, act string) { + es := sv.EditState() if !es.HasSelected() { return } - sv := vv.SVG() - svoff := sv.Root().BBox.Min + // svoff := sv.Root().BBox.Min sv.UndoSave(act, es.SelectedNamesString()) - abb, an := vv.AlignAnchorBBox(aa) sc := math32.Vec2(1, 1) odim := math32.OtherDim(dim) for sn := range es.Selected { @@ -148,100 +145,53 @@ func (vv *Canvas) AlignMin(aa AlignAnchors, dim math32.Dims, act string) { continue } sng := sn.AsNodeBase() - bb := sng.BBox.Sub(svoff) - del := math32.FromPoint(abb.Min.Sub(bb.Min)) + bb := sng.BBox // .Sub(svoff) + del := apos.Sub(bb.Min) del.SetDim(odim, 0) - sn.ApplyDeltaTransform(vv.SSVG(), del, sc, 0, math32.FromPoint(bb.Min)) + sn.ApplyDeltaTransform(sv.SVG, del, sc, 0, bb.Min) } - sv.UpdateView(true) - vv.ChangeMade() } -func (vv *Canvas) AlignMinAnchor(aa AlignAnchors, dim math32.Dims, act string) { - es := &vv.EditState - if !es.HasSelected() { +// AlignMin aligns to min coordinate (Left, Top) in bbox +func (sv *SVG) AlignMin(aa AlignAnchors, dim math32.Dims, act string) { + if !sv.EditState().HasSelected() { return } - sv := vv.SVG() - svoff := sv.Root().BBox.Min - sv.UndoSave(act, es.SelectedNamesString()) - abb, an := vv.AlignAnchorBBox(aa) - sc := math32.Vec2(1, 1) - odim := math32.OtherDim(dim) - for sn := range es.Selected { - if sn == an { - continue - } - sng := sn.AsNodeBase() - bb := sng.BBox.Sub(svoff) - del := math32.FromPoint(abb.Max.Sub(bb.Min)) - del.SetDim(odim, 0) - sn.ApplyDeltaTransform(vv.SSVG(), del, sc, 0, math32.FromPoint(bb.Min)) - } - sv.UpdateView(true) - vv.ChangeMade() + abb, an := sv.alignAnchorBBox(aa) + sv.alignImpl(abb.Min, an, dim, act) } -func (vv *Canvas) AlignMax(aa AlignAnchors, dim math32.Dims, act string) { - es := &vv.EditState - if !es.HasSelected() { +func (sv *SVG) AlignMinAnchor(aa AlignAnchors, dim math32.Dims, act string) { + if !sv.EditState().HasSelected() { return } - sv := vv.SVG() - svoff := sv.Root().BBox.Min - sv.UndoSave(act, es.SelectedNamesString()) - abb, an := vv.AlignAnchorBBox(aa) - sc := math32.Vec2(1, 1) - odim := math32.OtherDim(dim) - for sn := range es.Selected { - if sn == an { - continue - } - sng := sn.AsNodeBase() - bb := sng.BBox.Sub(svoff) - del := math32.FromPoint(abb.Max.Sub(bb.Max)) - del.SetDim(odim, 0) - sn.ApplyDeltaTransform(vv.SSVG(), del, sc, 0, math32.FromPoint(bb.Min)) - } - sv.UpdateView(true) - vv.ChangeMade() + abb, an := sv.alignAnchorBBox(aa) + sv.alignImpl(abb.Max, an, dim, act) } -func (vv *Canvas) AlignMaxAnchor(aa AlignAnchors, dim math32.Dims, act string) { - es := &vv.EditState - if !es.HasSelected() { +func (sv *SVG) AlignMax(aa AlignAnchors, dim math32.Dims, act string) { + if !sv.EditState().HasSelected() { return } - sv := vv.SVG() - svoff := sv.Root().BBox.Min - sv.UndoSave(act, es.SelectedNamesString()) - abb, an := vv.AlignAnchorBBox(aa) - sc := math32.Vec2(1, 1) - odim := math32.OtherDim(dim) - for sn := range es.Selected { - if sn == an { - continue - } - sng := sn.AsNodeBase() - bb := sng.BBox.Sub(svoff) - del := math32.FromPoint(abb.Min.Sub(bb.Max)) - del.SetDim(odim, 0) - sn.ApplyDeltaTransform(vv.SSVG(), del, sc, 0, math32.FromPoint(bb.Min)) + abb, an := sv.alignAnchorBBox(aa) + sv.alignImpl(abb.Max, an, dim, act) +} + +func (sv *SVG) AlignMaxAnchor(aa AlignAnchors, dim math32.Dims, act string) { + if !sv.EditState().HasSelected() { + return } - sv.UpdateView(true) - vv.ChangeMade() + abb, an := sv.alignAnchorBBox(aa) + sv.alignImpl(abb.Min, an, dim, act) } -func (vv *Canvas) AlignCenter(aa AlignAnchors, dim math32.Dims, act string) { - es := &vv.EditState +func (sv *SVG) AlignCenter(aa AlignAnchors, dim math32.Dims, act string) { + es := sv.EditState() if !es.HasSelected() { return } - sv := vv.SVG() - svoff := sv.Root().BBox.Min - sv.UndoSave(act, es.SelectedNamesString()) - abb, an := vv.AlignAnchorBBox(aa) - ctr := math32.FromPoint(abb.Min.Add(abb.Max)).MulScalar(0.5) + abb, an := sv.alignAnchorBBox(aa) + ctr := abb.Min.Add(abb.Max).MulScalar(0.5) sc := math32.Vec2(1, 1) odim := math32.OtherDim(dim) for sn := range es.Selected { @@ -249,14 +199,14 @@ func (vv *Canvas) AlignCenter(aa AlignAnchors, dim math32.Dims, act string) { continue } sng := sn.AsNodeBase() - bb := sng.BBox.Sub(svoff) - nctr := math32.FromPoint(bb.Min.Add(bb.Max)).MulScalar(0.5) + bb := sng.BBox // .Sub(svoff) + nctr := bb.Min.Add(bb.Max).MulScalar(0.5) del := ctr.Sub(nctr) del.SetDim(odim, 0) - sn.ApplyDeltaTransform(vv.SSVG(), del, sc, 0, math32.FromPoint(bb.Min)) + sn.ApplyDeltaTransform(sv.SVG, del, sc, 0, bb.Min) } sv.UpdateView(true) - vv.ChangeMade() + sv.Canvas.ChangeMade() } // GatherAlignPoints gets all the potential points of alignment for objects not @@ -282,7 +232,7 @@ func (sv *SVG) GatherAlignPoints() { return tree.Break // go no further into kids } for ap := BBLeft; ap < BBoxPointsN; ap++ { - es.AlignPts[ap] = append(es.AlignPts[ap], ap.PointRect(nb.BBox)) + es.AlignPts[ap] = append(es.AlignPts[ap], ap.PointBox(nb.BBox)) } return tree.Continue }) diff --git a/canvas/bbox.go b/canvas/bbox.go index 4f95c601..d55e20c3 100644 --- a/canvas/bbox.go +++ b/canvas/bbox.go @@ -5,8 +5,6 @@ package canvas import ( - "image" - "cogentcore.org/core/math32" ) @@ -22,26 +20,6 @@ const ( BBBottom ) -// ValueRect returns the relevant value for a given bounding box -// as an image.Rectangle -func (ev BBoxPoints) ValueRect(bb image.Rectangle) float32 { - switch ev { - case BBLeft: - return float32(bb.Min.X) - case BBCenter: - return 0.5 * float32(bb.Min.X+bb.Max.X) - case BBRight: - return float32(bb.Max.X) - case BBTop: - return float32(bb.Min.Y) - case BBMiddle: - return 0.5 * float32(bb.Min.Y+bb.Max.Y) - case BBBottom: - return float32(bb.Max.Y) - } - return 0 -} - // ValueBox returns the relevant value for a given bounding box as a // math32.Box2 func (ev BBoxPoints) ValueBox(bb math32.Box2) float32 { @@ -115,22 +93,8 @@ func ReshapeBBoxPoints(reshape Sprites) (bbX, bbY BBoxPoints) { return } -// PointRect returns the relevant point for a given bounding box, where -// relevant dimension is from ValRect and other is midpoint -- for drawing lines. -// BBox is an image.Rectangle -func (ev BBoxPoints) PointRect(bb image.Rectangle) math32.Vector2 { - val := ev.ValueRect(bb) - switch ev { - case BBLeft, BBCenter, BBRight: - return math32.Vec2(val, 0.5*float32(bb.Min.Y+bb.Max.Y)) - default: - return math32.Vec2(0.5*float32(bb.Min.X+bb.Max.X), val) - } -} - // PointBox returns the relevant point for a given bounding box, where // relevant dimension is from ValRect and other is midpoint -- for drawing lines. -// BBox is an image.Rectangle func (ev BBoxPoints) PointBox(bb math32.Box2) math32.Vector2 { val := ev.ValueBox(bb) switch ev { diff --git a/canvas/canvas.go b/canvas/canvas.go index 88840df3..e4f8293e 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package canvas implements a 2D vector graphics program. +// Package canvas implements a 2D vector graphics editor. package canvas //go:generate core generate @@ -36,6 +36,16 @@ type Canvas struct { // current edit state EditState EditState `set:"-"` + + SVG *SVG + tabs *core.Tabs + splits *core.Splits + modalTools *core.Toolbar + tools *core.Toolbar + tree *Tree + layerTree *core.Frame + layers *core.Table + statusBar *core.Frame } func (cv *Canvas) Init() { @@ -66,6 +76,7 @@ func (cv *Canvas) Init() { }) tree.AddChildAt(cv, "modal-tb", func(w *core.Toolbar) { + cv.modalTools = w w.Maker(func(p *tree.Plan) { switch cv.EditState.Tool { case NodeTool: @@ -83,6 +94,7 @@ func (cv *Canvas) Init() { s.Grow.Set(1, 1) }) tree.AddChildAt(w, "tools", func(w *core.Toolbar) { + cv.tools = w w.Styler(func(s *styles.Style) { s.Direction = styles.Column }) @@ -91,6 +103,7 @@ func (cv *Canvas) Init() { tree.AddChildAt(w, "splits", func(w *core.Splits) { w.SetSplits(0.15, 0.60, 0.25) tree.AddChildAt(w, "layer-tree", func(w *core.Frame) { + cv.layerTree = w w.Styler(func(s *styles.Style) { s.Direction = styles.Column }) @@ -98,6 +111,7 @@ func (cv *Canvas) Init() { w.SetFunc(cv.AddLayer) }) tree.AddChildAt(w, "layers", func(w *core.Table) { + cv.layers = w w.Styler(func(s *styles.Style) { s.Max.Y.Em(10) }) @@ -109,19 +123,22 @@ func (cv *Canvas) Init() { s.Grow.Set(0, 1) }) tree.AddChildAt(w, "tree", func(w *Tree) { + cv.tree = w w.Canvas = cv w.OpenDepth = 4 - w.SyncTree(cv.SVG().Root()) + w.SyncTree(cv.SVG.Root()) }) }) }) tree.AddChildAt(w, "svg", func(w *SVG) { + cv.SVG = w w.Canvas = cv w.UpdateGradients(cv.EditState.Gradients) cv.SetPhysSize(&Settings.Size) cv.SyncLayers() }) tree.AddChildAt(w, "tabs", func(w *core.Tabs) { + cv.tabs = w w.SetType(core.FunctionalTabs) pt, _ := w.NewTab("Paint") NewPaintView(pt).SetCanvas(cv) @@ -134,6 +151,7 @@ func (cv *Canvas) Init() { }) }) tree.AddChildAt(cv, "status-bar", func(w *core.Frame) { + cv.statusBar = w w.Styler(func(s *styles.Style) { s.Grow.Set(1, 0) }) @@ -148,7 +166,7 @@ func (cv *Canvas) Init() { // if sig == int64(core.TreeInserted) { // sn, ok := data.(svg.Node) // if ok { - // gvv.SVG().NodeEnsureUniqueID(sn) + // gvv.SVG.NodeEnsureUniqueID(sn) // svg.CloneNodeGradientProp(sn, "fill") // svg.CloneNodeGradientProp(sn, "stroke") // } @@ -187,7 +205,7 @@ func (cv *Canvas) Init() { func (cv *Canvas) OpenDrawingFile(fnm core.Filename) error { path, _ := filepath.Abs(string(fnm)) cv.Filename = core.Filename(path) - sv := cv.SVG() + sv := cv.SVG err := errors.Log(sv.SVG.OpenXML(path)) // SavedPaths.AddPath(path, core.Settings.Params.SavedPathsMax) // SavePaths() @@ -207,9 +225,9 @@ func (cv *Canvas) OpenDrawingFile(fnm core.Filename) error { func (cv *Canvas) OpenDrawing(fnm core.Filename) error { //types:add err := cv.OpenDrawingFile(fnm) - sv := cv.SVG() + sv := cv.SVG cv.SetTitle() - tv := cv.Tree() + tv := cv.tree tv.CloseAll() tv.Resync() cv.SetStatus("Opened: " + string(cv.Filename)) @@ -228,7 +246,7 @@ func (cv *Canvas) NewDrawing(sz PhysSize) *Canvas { // PromptPhysSize prompts for the physical size of the drawing and sets it func (cv *Canvas) PromptPhysSize() { //types:add - sv := cv.SVG() + sv := cv.SVG sz := &PhysSize{} sz.SetFromSVG(sv) d := core.NewBody("SVG physical size") @@ -252,10 +270,10 @@ func (cv *Canvas) SetPhysSize(sz *PhysSize) { if sz.Size == (math32.Vector2{}) { sz.SetStandardSize(Settings.Size.StandardSize) } - sv := cv.SVG() + sv := cv.SVG sz.SetToSVG(sv) sv.SetMetaData() - sv.ZoomToPage(false) + sv.ResetZoom() } // SaveDrawing saves .svg drawing to current filename @@ -276,7 +294,7 @@ func (cv *Canvas) SaveDrawingAs(fname core.Filename) error { //types:add cv.Filename = core.Filename(path) // SavedPaths.AddPath(path, core.Settings.Params.SavedPathsMax) // SavePaths() - sv := cv.SVG() + sv := cv.SVG sv.SVG.RemoveOrphanedDefs() sv.SetMetaData() err := sv.SVG.SaveXML(path) @@ -299,7 +317,7 @@ func (cv *Canvas) SaveDrawingAs(fname core.Filename) error { //types:add func (cv *Canvas) ExportPNG(width, height float32) error { //types:add path, _ := filepath.Split(string(cv.Filename)) fnm := filepath.Join(path, "export_png.svg") - sv := cv.SVG() + sv := cv.SVG err := sv.SVG.SaveXML(fnm) if errors.Log(err) != nil { return err @@ -333,7 +351,7 @@ func (cv *Canvas) ExportPNG(width, height float32) error { //types:add func (cv *Canvas) ExportPDF(dpi float32) error { //types:add path, _ := filepath.Split(string(cv.Filename)) fnm := filepath.Join(path, "export_pdf.svg") - sv := cv.SVG() + sv := cv.SVG err := sv.SVG.SaveXML(fnm) if errors.Log(err) != nil { return err @@ -361,14 +379,14 @@ func (cv *Canvas) ExportPDF(dpi float32) error { //types:add // preserving the current grid offset, so grid snapping // is preserved. func (cv *Canvas) ResizeToContents() { //types:add - sv := cv.SVG() + sv := cv.SVG sv.ResizeToContents(true) sv.UpdateView(true) } // AddImage adds a new image node set to the given image func (cv *Canvas) AddImage(fname core.Filename, width, height float32) error { //types:add - sv := cv.SVG() + sv := cv.SVG sv.UndoSave("AddImage", string(fname)) ind := NewSVGElement[svg.Image](sv) ind.Pos.X = 100 // todo: default pos @@ -379,132 +397,90 @@ func (cv *Canvas) AddImage(fname core.Filename, width, height float32) error { / return err } -func (cv *Canvas) ModalToolbar() *core.Toolbar { - return cv.ChildByName("modal-tb", 1).(*core.Toolbar) -} - -func (cv *Canvas) HBox() *core.Frame { - return cv.ChildByName("hbox", 2).(*core.Frame) -} - -func (cv *Canvas) Tools() *core.Toolbar { - return cv.HBox().ChildByName("tools", 0).(*core.Toolbar) -} - -func (cv *Canvas) Splits() *core.Splits { - return cv.HBox().ChildByName("splits", 1).(*core.Splits) -} - -func (cv *Canvas) LayerTree() *core.Frame { - return cv.Splits().ChildByName("layer-tree", 0).(*core.Frame) -} - -func (vv *Canvas) LayerView() *core.Table { - return vv.LayerTree().ChildByName("layers", 0).(*core.Table) -} - -func (vv *Canvas) Tree() *Tree { - return vv.LayerTree().ChildByName("tree-frame", 1).AsTree().Child(0).(*Tree) -} - -// SVG returns the [SVG]. -func (vv *Canvas) SVG() *SVG { - return vv.Splits().Child(1).(*SVG) -} - // SSVG returns the underlying [svg.SVG]. -func (vv *Canvas) SSVG() *svg.SVG { - return vv.SVG().SVG -} - -func (vv *Canvas) Tabs() *core.Tabs { - return vv.Splits().ChildByName("tabs", 2).(*core.Tabs) -} - -// StatusBar returns the status bar widget -func (vv *Canvas) StatusBar() *core.Frame { - return vv.ChildByName("status-bar", 4).(*core.Frame) +func (cv *Canvas) SSVG() *svg.SVG { + return cv.SVG.SVG } // StatusText returns the status bar text widget -func (vv *Canvas) StatusText() *core.Text { - return vv.StatusBar().Child(0).(*core.Text) +func (cv *Canvas) StatusText() *core.Text { + return cv.statusBar.Child(0).(*core.Text) } // PasteAvailFunc is an ActionUpdateFunc that inactivates action if no paste avail -func (vv *Canvas) PasteAvailFunc(bt *core.Button) { - bt.SetEnabled(!vv.Clipboard().IsEmpty()) +func (cv *Canvas) PasteAvailFunc(bt *core.Button) { + bt.SetEnabled(!cv.Clipboard().IsEmpty()) } -func (vv *Canvas) MakeToolbar(p *tree.Plan) { +func (cv *Canvas) MakeToolbar(p *tree.Plan) { tree.Add(p, func(w *core.FuncButton) { // TODO(kai): remove Update - w.SetFunc(vv.UpdateAll).SetText("Update").SetIcon(icons.Update) + w.SetFunc(cv.UpdateAll).SetText("Update").SetIcon(icons.Update) }) tree.Add(p, func(w *core.Button) { w.SetText("New").SetIcon(icons.Add). OnClick(func(e events.Event) { - ndr := vv.NewDrawing(Settings.Size) + ndr := cv.NewDrawing(Settings.Size) ndr.PromptPhysSize() }) }) tree.Add(p, func(w *core.Button) { w.SetText("Size").SetIcon(icons.FormatSize).SetMenu(func(m *core.Scene) { - core.NewFuncButton(m).SetFunc(vv.PromptPhysSize).SetText("Set size").SetIcon(icons.FormatSize) - core.NewFuncButton(m).SetFunc(vv.ResizeToContents).SetIcon(icons.Resize) + core.NewFuncButton(m).SetFunc(cv.PromptPhysSize).SetText("Set size").SetIcon(icons.FormatSize) + core.NewFuncButton(m).SetFunc(cv.ResizeToContents).SetIcon(icons.Resize) }) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.OpenDrawing).SetText("Open").SetIcon(icons.Open) + w.SetFunc(cv.OpenDrawing).SetText("Open").SetIcon(icons.Open) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.SaveDrawing).SetText("Save").SetIcon(icons.Save) + w.SetFunc(cv.SaveDrawing).SetText("Save").SetIcon(icons.Save) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.SaveDrawingAs).SetText("Save as").SetIcon(icons.SaveAs) + w.SetFunc(cv.SaveDrawingAs).SetText("Save as").SetIcon(icons.SaveAs) }) tree.Add(p, func(w *core.Button) { w.SetText("Export").SetIcon(icons.ExportNotes).SetMenu(func(m *core.Scene) { - core.NewFuncButton(m).SetFunc(vv.ExportPNG).SetIcon(icons.Image) - core.NewFuncButton(m).SetFunc(vv.ExportPDF).SetIcon(icons.PictureAsPdf) + core.NewFuncButton(m).SetFunc(cv.ExportPNG).SetIcon(icons.Image) + core.NewFuncButton(m).SetFunc(cv.ExportPDF).SetIcon(icons.PictureAsPdf) }) }) tree.Add(p, func(w *core.Separator) {}) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.Undo).SetIcon(icons.Undo) + w.SetFunc(cv.Undo).SetIcon(icons.Undo) w.FirstStyler(func(s *styles.Style) { - s.SetEnabled(vv.EditState.Undos.HasUndoAvail()) + s.SetEnabled(cv.EditState.Undos.HasUndoAvail()) }) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.Redo).SetIcon(icons.Redo) + w.SetFunc(cv.Redo).SetIcon(icons.Redo) w.FirstStyler(func(s *styles.Style) { - s.SetEnabled(vv.EditState.Undos.HasRedoAvail()) + s.SetEnabled(cv.EditState.Undos.HasRedoAvail()) }) }) tree.Add(p, func(w *core.Separator) {}) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.DuplicateSelected).SetText("Duplicate").SetIcon(icons.Copy).SetKey(keymap.Duplicate) + w.SetFunc(cv.DuplicateSelected).SetText("Duplicate").SetIcon(icons.Copy).SetKey(keymap.Duplicate) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.CopySelected).SetText("Copy").SetIcon(icons.Copy).SetKey(keymap.Copy) + w.SetFunc(cv.CopySelected).SetText("Copy").SetIcon(icons.Copy).SetKey(keymap.Copy) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.CutSelected).SetText("Cut").SetIcon(icons.Cut).SetKey(keymap.Cut) + w.SetFunc(cv.CutSelected).SetText("Cut").SetIcon(icons.Cut).SetKey(keymap.Cut) }) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.PasteClip).SetText("Paste").SetIcon(icons.Paste).SetKey(keymap.Paste) + w.SetFunc(cv.PasteClip).SetText("Paste").SetIcon(icons.Paste).SetKey(keymap.Paste) }) tree.Add(p, func(w *core.Separator) {}) tree.Add(p, func(w *core.FuncButton) { - w.SetFunc(vv.AddImage).SetIcon(icons.Image) + w.SetFunc(cv.AddImage).SetIcon(icons.Image) }) tree.Add(p, func(w *core.Separator) {}) @@ -512,8 +488,8 @@ func (vv *Canvas) MakeToolbar(p *tree.Plan) { w.SetText("Zoom page").SetIcon(icons.ZoomOut) w.SetTooltip("Zoom to see the entire page size for drawing") w.OnClick(func(e events.Event) { - sv := vv.SVG() - sv.ZoomToPage(false) + sv := cv.SVG + sv.ResetZoom() sv.UpdateView(true) }) }) @@ -521,7 +497,7 @@ func (vv *Canvas) MakeToolbar(p *tree.Plan) { w.SetText("Zoom all").SetIcon(icons.ZoomOut) w.SetTooltip("Zoom to see all elements") w.OnClick(func(e events.Event) { - sv := vv.SVG() + sv := cv.SVG sv.ZoomToContents(false) }) }) @@ -529,7 +505,7 @@ func (vv *Canvas) MakeToolbar(p *tree.Plan) { // SetStatus updates the status bar text with the given message, along with other status info func (cv *Canvas) SetStatus(msg string) { - sb := cv.StatusBar() + sb := cv.statusBar if sb == nil { return } @@ -543,12 +519,12 @@ func (cv *Canvas) SetStatus(msg string) { text.SetText(str).UpdateRender() } -func (vv *Canvas) SetTitle() { - if vv.Filename == "" { +func (cv *Canvas) SetTitle() { + if cv.Filename == "" { return } - dfnm := fsx.DirAndFile(string(vv.Filename)) - vv.Scene.Body.SetTitle("Cogent Canvas • " + dfnm) + dfnm := fsx.DirAndFile(string(cv.Filename)) + cv.Scene.Body.SetTitle("Cogent Canvas • " + dfnm) } // NewDrawing opens a new drawing window @@ -570,10 +546,10 @@ func NewWindow(fnm string) *Canvas { if w := core.AllRenderWindows.FindName(winm); w != nil { sc := w.MainScene() - if vv := tree.ChildByType[*Canvas](sc.Body); vv != nil { - if string(vv.Filename) == path { + if cv := tree.ChildByType[*Canvas](sc.Body); cv != nil { + if string(cv.Filename) == path { w.Raise() - return vv + return cv } } } @@ -603,31 +579,31 @@ func NewWindow(fnm string) *Canvas { // Tab returns the tab with the given name func (gv *Canvas) Tab(name string) *core.Frame { - return gv.Tabs().TabByName(name) + return gv.tabs.TabByName(name) } -func (vv *Canvas) PaintView() *PaintView { - return vv.Tab("Paint").Child(0).(*PaintView) +func (cv *Canvas) PaintView() *PaintView { + return cv.Tab("Paint").Child(0).(*PaintView) } // UpdateAll updates the display -func (vv *Canvas) UpdateAll() { //types:add - vv.UpdateTabs() - vv.UpdateTree() - vv.UpdateDisp() +func (cv *Canvas) UpdateAll() { //types:add + cv.UpdateTabs() + cv.UpdateTree() + cv.UpdateDisp() } -func (vv *Canvas) UpdateDisp() { - sv := vv.SVG() +func (cv *Canvas) UpdateDisp() { + sv := cv.SVG sv.UpdateView(true) } -func (vv *Canvas) UpdateTree() { - tv := vv.Tree() +func (cv *Canvas) UpdateTree() { + tv := cv.tree tv.Resync() } -func (vv *Canvas) SetDefaultStyle() { +func (cv *Canvas) SetDefaultStyle() { // pv := vv.PaintView() // es := &vv.EditState // switch es.Tool { @@ -640,7 +616,7 @@ func (vv *Canvas) SetDefaultStyle() { // } } -func (vv *Canvas) UpdateTabs() { +func (cv *Canvas) UpdateTabs() { // es := &vv.EditState // fsel := es.FirstSelectedNode() // if fsel != nil { @@ -662,47 +638,47 @@ func (vv *Canvas) UpdateTabs() { } // SelectNodeInSVG selects given svg node in SVG drawing -func (vv *Canvas) SelectNodeInSVG(kn tree.Node, mode events.SelectModes) { +func (cv *Canvas) SelectNodeInSVG(kn tree.Node, mode events.SelectModes) { sii, ok := kn.(svg.Node) if !ok { return } - sv := vv.SVG() - es := &vv.EditState + sv := cv.SVG + es := &cv.EditState es.SelectAction(sii, mode, image.Point{}) sv.UpdateView(false) } // Undo undoes the last action -func (vv *Canvas) Undo() string { //types:add - sv := vv.SVG() +func (cv *Canvas) Undo() string { //types:add + sv := cv.SVG act := sv.Undo() if act != "" { - vv.SetStatus("Undid: " + act) + cv.SetStatus("Undid: " + act) } else { - vv.SetStatus("Undo: no more to undo") + cv.SetStatus("Undo: no more to undo") } - vv.UpdateAll() + cv.UpdateAll() return act } // Redo redoes the previously undone action -func (vv *Canvas) Redo() string { //types:add - sv := vv.SVG() +func (cv *Canvas) Redo() string { //types:add + sv := cv.SVG act := sv.Redo() if act != "" { - vv.SetStatus("Redid: " + act) + cv.SetStatus("Redid: " + act) } else { - vv.SetStatus("Redo: no more to redo") + cv.SetStatus("Redo: no more to redo") } - vv.UpdateAll() + cv.UpdateAll() return act } // ChangeMade should be called after any change is completed on the drawing. // Calls autosave. -func (vv *Canvas) ChangeMade() { - go vv.AutoSave() +func (cv *Canvas) ChangeMade() { + go cv.AutoSave() } ///////////////////////////////////////////////////////////////////////// @@ -720,7 +696,7 @@ func (gv *Canvas) OSFileEvent() { */ // OpenRecent opens a recently used file -func (vv *Canvas) OpenRecent(filename core.Filename) { +func (cv *Canvas) OpenRecent(filename core.Filename) { // if string(filename) == VectorResetRecents { // SavedPaths = nil // core.StringsAddExtras((*[]string)(&SavedPaths), SavedPathsExtras) @@ -732,7 +708,7 @@ func (vv *Canvas) OpenRecent(filename core.Filename) { } // RecentsEdit opens a dialog editor for deleting from the recents project list -func (vv *Canvas) EditRecents() { +func (cv *Canvas) EditRecents() { // tmp := make([]string, len(SavedPaths)) // copy(tmp, SavedPaths) // core.StringsRemoveExtras((*[]string)(&tmp), SavedPathsExtras) @@ -748,8 +724,8 @@ func (vv *Canvas) EditRecents() { } // SplitsSetView sets split view splitters to given named setting -func (vv *Canvas) SplitsSetView(split SplitName) { - sv := vv.Splits() +func (cv *Canvas) SplitsSetView(split SplitName) { + sv := cv.splits sp, _, ok := AvailableSplits.SplitByName(split) if ok { sv.SetSplits(sp.Splits...).NeedsLayout() @@ -759,8 +735,8 @@ func (vv *Canvas) SplitsSetView(split SplitName) { // SplitsSave saves current splitter settings to named splitter settings under // existing name, and saves to prefs file -func (vv *Canvas) SplitsSave(split SplitName) { - sv := vv.Splits() +func (cv *Canvas) SplitsSave(split SplitName) { + sv := cv.splits sp, _, ok := AvailableSplits.SplitByName(split) if ok { sp.SaveSplits(sv.Splits()) @@ -770,19 +746,19 @@ func (vv *Canvas) SplitsSave(split SplitName) { // SplitsSaveAs saves current splitter settings to new named splitter settings, and // saves to prefs file -func (vv *Canvas) SplitsSaveAs(name, desc string) { - spv := vv.Splits() +func (cv *Canvas) SplitsSaveAs(name, desc string) { + spv := cv.splits AvailableSplits.Add(name, desc, spv.Splits()) AvailableSplits.SaveSettings() } // SplitsEdit opens the SplitsView editor to customize saved splitter settings -func (vv *Canvas) SplitsEdit() { +func (cv *Canvas) SplitsEdit() { SplitsView(&AvailableSplits) } // HelpWiki opens wiki page for grid on github -func (vv *Canvas) HelpWiki() { +func (cv *Canvas) HelpWiki() { core.TheApp.OpenURL("https://vector.cogentcore.org") } @@ -790,23 +766,23 @@ func (vv *Canvas) HelpWiki() { // AutoSave // AutoSaveFilename returns the autosave filename -func (vv *Canvas) AutoSaveFilename() string { - path, fn := filepath.Split(string(vv.Filename)) +func (cv *Canvas) AutoSaveFilename() string { + path, fn := filepath.Split(string(cv.Filename)) if fn == "" { - fn = "new_file_" + vv.Name + ".svg" + fn = "new_file_" + cv.Name + ".svg" } asfn := filepath.Join(path, "#"+fn+"#") return asfn } // AutoSave does the autosave -- safe to call in a separate goroutine -func (vv *Canvas) AutoSave() error { +func (cv *Canvas) AutoSave() error { // if vv.HasFlag(int(VectorAutoSaving)) { // return nil // } // vv.SetFlag(int(VectorAutoSaving)) // asfn := vv.AutoSaveFilename() - // sv := vv.SVG() + // sv := vv.SVG // err := sv.SaveXML(core.Filename(asfn)) // if err != nil && err != io.EOF { // log.Println(err) @@ -817,15 +793,15 @@ func (vv *Canvas) AutoSave() error { } // AutoSaveDelete deletes any existing autosave file -func (vv *Canvas) AutoSaveDelete() { - asfn := vv.AutoSaveFilename() +func (cv *Canvas) AutoSaveDelete() { + asfn := cv.AutoSaveFilename() os.Remove(asfn) } // AutoSaveCheck checks if an autosave file exists -- logic for dealing with // it is left to larger app -- call this before opening a file -func (vv *Canvas) AutoSaveCheck() bool { - asfn := vv.AutoSaveFilename() +func (cv *Canvas) AutoSaveCheck() bool { + asfn := cv.AutoSaveFilename() if _, err := os.Stat(asfn); os.IsNotExist(err) { return false // does not exist } diff --git a/canvas/edits.go b/canvas/edits.go index 0ec34aeb..adafdfcc 100644 --- a/canvas/edits.go +++ b/canvas/edits.go @@ -399,8 +399,7 @@ func (es *EditState) UpdateSelectBBox() { bbox.SetEmpty() for itm := range es.Selected { g := itm.AsNodeBase() - bb := math32.Box2{} - bb.SetFromRect(g.BBox) + bb := g.BBox bbox.ExpandByBox(bb) } es.SelectBBox = bbox diff --git a/canvas/layers.go b/canvas/layers.go index a78558c3..9f33f7c8 100644 --- a/canvas/layers.go +++ b/canvas/layers.go @@ -77,8 +77,8 @@ func (ly *Layers) LayerIndexByName(nm string) int { } // FirstLayerIndex returns index of first layer group in svg -func (vv *Canvas) FirstLayerIndex() int { - sv := vv.SVG() +func (cv *Canvas) FirstLayerIndex() int { + sv := cv.SVG for i, kc := range sv.Root().Children { if NodeIsLayer(kc) { return i @@ -87,9 +87,9 @@ func (vv *Canvas) FirstLayerIndex() int { return min(1, len(sv.Root().Children)) } -func (vv *Canvas) LayerViewSigs(lyv *core.Table) { +func (cv *Canvas) LayerViewSigs(lyv *core.Table) { // es := &gv.EditState - // sv := gv.SVG() + // sv := gv.SVG // lyv.ViewSig.Connect(gv.This, func(recv, send tree.Node, sig int64, data any) { // // fmt.Printf("tv viewsig: %v data: %v send: %v\n", sig, data, send.Path()) // update := sv.UpdateStart() @@ -127,16 +127,16 @@ func (vv *Canvas) LayerViewSigs(lyv *core.Table) { // }) } -func (vv *Canvas) SyncLayers() { - sv := vv.SVG() - vv.EditState.Layers.SyncLayers(sv) +func (cv *Canvas) SyncLayers() { + sv := cv.SVG + cv.EditState.Layers.SyncLayers(sv) } -func (vv *Canvas) UpdateLayerView() { - vv.SyncLayers() - es := &vv.EditState +func (cv *Canvas) UpdateLayerView() { + cv.SyncLayers() + es := &cv.EditState lys := &es.Layers - lyv := vv.LayerView() + lyv := cv.layers lyv.SetSlice(lys) nl := len(*lys) if nl == 0 { @@ -153,11 +153,11 @@ func (vv *Canvas) UpdateLayerView() { } // AddLayer adds a new layer -func (vv *Canvas) AddLayer() { //types:add - sv := vv.SVG() +func (cv *Canvas) AddLayer() { //types:add + sv := cv.SVG svr := sv.Root() - lys := &vv.EditState.Layers + lys := &cv.EditState.Layers lys.SyncLayers(sv) nl := len(*lys) si := 1 // starting index -- assuming namedview @@ -175,15 +175,15 @@ func (vv *Canvas) AddLayer() { //types:add kc := svr.Child(i) tree.MoveToParent(kc, l1) } - vv.SetCurLayer(l1.AsTree().Name) + cv.SetCurLayer(l1.AsTree().Name) } else { l1 := svg.NewGroup() svr.InsertChild(l1, si+nl) l1.AsTree().SetName(fmt.Sprintf("Layer%d", nl)) l1.AsTree().SetProperty("groupmode", "layer") - vv.SetCurLayer(l1.AsTree().Name) + cv.SetCurLayer(l1.AsTree().Name) } - vv.UpdateLayerView() + cv.UpdateLayerView() } ///////////////////////////////////////////////////////////////// @@ -222,21 +222,21 @@ func NodeParentLayer(n tree.Node) tree.Node { // IsCurLayer returns true if given layer is the current layer // for creating items -func (vv *Canvas) IsCurLayer(lay string) bool { - return vv.EditState.CurLayer == lay +func (cv *Canvas) IsCurLayer(lay string) bool { + return cv.EditState.CurLayer == lay } // SetCurLayer sets the current layer for creating items to given one -func (vv *Canvas) SetCurLayer(lay string) { - vv.EditState.CurLayer = lay - vv.SetStatus("set current layer to: " + lay) +func (cv *Canvas) SetCurLayer(lay string) { + cv.EditState.CurLayer = lay + cv.SetStatus("set current layer to: " + lay) } // ClearCurLayer clears the current layer for creating items if it // was set to the given layer name -func (vv *Canvas) ClearCurLayer(lay string) { - if vv.EditState.CurLayer == lay { - vv.EditState.CurLayer = "" - vv.SetStatus("clear current layer from: " + lay) +func (cv *Canvas) ClearCurLayer(lay string) { + if cv.EditState.CurLayer == lay { + cv.EditState.CurLayer = "" + cv.SetStatus("clear current layer from: " + lay) } } diff --git a/canvas/manip.go b/canvas/manip.go index d065912b..43197fff 100644 --- a/canvas/manip.go +++ b/canvas/manip.go @@ -32,10 +32,9 @@ func (sv *SVG) ManipDone() { es := sv.EditState() switch { case es.Action == BoxSelect: - bbox := image.Rectangle{Min: es.DragStartPos, Max: es.DragCurPos} + bbox := math32.Box2{Min: math32.FromPoint(es.DragStartPos), Max: math32.FromPoint(es.DragCurPos)} bbox = bbox.Canon() InactivateSprites(sprites, SpRubberBand) - fmt.Println(bbox) sel := sv.SelectWithinBBox(bbox, false) if len(sel) > 0 { es.ResetSelected() // todo: extend select -- need mouse mod diff --git a/canvas/paint.go b/canvas/paint.go index dd3de595..80890628 100644 --- a/canvas/paint.go +++ b/canvas/paint.go @@ -320,7 +320,7 @@ func (pv *PaintView) Init() { // runs given function to actually do the update. func (vv *Canvas) ManipAction(act Actions, data string, manip bool, fun func(sii svg.Node)) { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG // update := false actStart := false finalAct := false @@ -392,7 +392,7 @@ func (vv *Canvas) SetColorNode(sii svg.Node, prop string, prev, pt PaintTypes, s // based on previous and current PaintType func (vv *Canvas) SetStroke(prev, pt PaintTypes, sp string) { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG sv.UndoSave("SetStroke", sp) for itm := range es.Selected { vv.SetColorNode(itm, "stroke", prev, pt, sp) @@ -418,7 +418,7 @@ func (vv *Canvas) SetStrokeWidthNode(sii svg.Node, wp string) { // manip means currently being manipulated -- don't save undo. func (vv *Canvas) SetStrokeWidth(wp string, manip bool) { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG if !manip { sv.UndoSave("SetStrokeWidth", wp) } @@ -453,7 +453,7 @@ func (vv *Canvas) SetMarkerNode(sii svg.Node, start, mid, end string, sc, mc, ec } return } - sv := vv.SVG() + sv := vv.SVG MarkerSetProp(sv.SVG, sii, "marker-start", start, sc) MarkerSetProp(sv.SVG, sii, "marker-mid", mid, mc) MarkerSetProp(sv.SVG, sii, "marker-end", end, ec) @@ -462,7 +462,7 @@ func (vv *Canvas) SetMarkerNode(sii svg.Node, start, mid, end string, sc, mc, ec // SetMarkerProperties sets the marker properties func (vv *Canvas) SetMarkerProperties(start, mid, end string, sc, mc, ec MarkerColors) { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG sv.UndoSave("SetMarkerProperties", start+" "+mid+" "+end) for itm := range es.Selected { vv.SetMarkerNode(itm, start, mid, end, sc, mc, ec) @@ -475,7 +475,7 @@ func (vv *Canvas) UpdateMarkerColors(sii svg.Node) { if sii == nil { return } - sv := vv.SVG() + sv := vv.SVG MarkerUpdateColorProp(sv.SVG, sii, "marker-start") MarkerUpdateColorProp(sv.SVG, sii, "marker-mid") MarkerUpdateColorProp(sv.SVG, sii, "marker-end") @@ -503,7 +503,7 @@ func (vv *Canvas) SetDashNode(sii svg.Node, dary []float64) { // SetDashProperties sets the dash properties func (vv *Canvas) SetDashProperties(dary []float64) { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG sv.UndoSave("SetDashProperties", "") // update := sv.UpdateStart() // sv.SetFullReRender() @@ -518,7 +518,7 @@ func (vv *Canvas) SetDashProperties(dary []float64) { // based on previous and current PaintType func (vv *Canvas) SetFill(prev, pt PaintTypes, fp string) { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG sv.UndoSave("SetFill", fp) // update := sv.UpdateStart() // sv.SetFullReRender() @@ -545,7 +545,7 @@ func (vv *Canvas) SetFillColor(fp string, manip bool) { // DefaultGradient returns the default gradient to use for setting stops func (vv *Canvas) DefaultGradient() string { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG if len(vv.EditState.Gradients) == 0 { es.ConfigDefaultGradient() sv.UpdateGradients(es.Gradients) @@ -556,7 +556,7 @@ func (vv *Canvas) DefaultGradient() string { // UpdateGradients updates gradients from EditState func (vv *Canvas) UpdateGradients() { es := &vv.EditState - sv := vv.SVG() + sv := vv.SVG // update := sv.UpdateStart() sv.UpdateGradients(es.Gradients) // sv.UpdateEnd(update) diff --git a/canvas/path.go b/canvas/path.go index 05fd3b3a..677b3aae 100644 --- a/canvas/path.go +++ b/canvas/path.go @@ -76,7 +76,7 @@ func (vv *Canvas) NodeSetXPos(xp float32) { if !es.HasSelected() { return } - sv := vv.SVG() + sv := vv.SVG sv.UndoSave("NodeToX", fmt.Sprintf("%g", xp)) // todo vv.ChangeMade() @@ -87,7 +87,7 @@ func (vv *Canvas) NodeSetYPos(yp float32) { if !es.HasSelected() { return } - sv := vv.SVG() + sv := vv.SVG sv.UndoSave("NodeToY", fmt.Sprintf("%g", yp)) // todo vv.ChangeMade() diff --git a/canvas/select.go b/canvas/select.go index 0f5cdb5d..a9afce60 100644 --- a/canvas/select.go +++ b/canvas/select.go @@ -199,11 +199,10 @@ func (sv *SVG) setSelSpritePos() { sl := es.SelectedList(false) for si, sii := range sl { sn := sii.AsNodeBase() - if sn.BBox.Size() == image.ZP { + if sn.BBox.Size() == (math32.Vector2{}) { continue } - bb := math32.Box2{} - bb.SetFromRect(sn.BBox) + bb := sn.BBox sv.SetBBoxSpritePos(SpSelBBox, si, bb) nbox++ } @@ -322,7 +321,7 @@ func (gv *Canvas) SelectGroup() { //types:add if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("Group", es.SelectedNamesString()) sl := es.SelectedListDepth(sv, false) // ascending depth order @@ -352,7 +351,7 @@ func (gv *Canvas) SelectUnGroup() { //types:add if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("UnGroup", es.SelectedNamesString()) sl := es.SelectedList(true) // true = descending = reverse order @@ -383,7 +382,7 @@ func (gv *Canvas) SelectRotate(deg float32) { if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("Rotate", fmt.Sprintf("%g", deg)) del := math32.Vector2{} @@ -391,9 +390,8 @@ func (gv *Canvas) SelectRotate(deg float32) { rot := math32.DegToRad(deg) for sn := range es.Selected { sng := sn.AsNodeBase() - sz := math32.FromPoint(sng.BBox.Size()) - mn := math32.FromPoint(sng.BBox.Min) - ctr := mn.Add(sz.MulScalar(.5)) + sz := sng.BBox.Size() + ctr := sng.BBox.Min.Add(sz.MulScalar(.5)) sn.ApplyDeltaTransform(sv.SVG, del, sc, rot, ctr) } sv.UpdateView(true) @@ -405,17 +403,15 @@ func (gv *Canvas) SelectScale(scx, scy float32) { if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("Scale", fmt.Sprintf("%g,%g", scx, scy)) - svoff := image.Point{} // sv.Geom.ContentBBox.Min del := math32.Vector2{} sc := math32.Vec2(scx, scy) for sn := range es.Selected { sng := sn.AsNodeBase() - sz := math32.FromPoint(sng.BBox.Size()) - mn := math32.FromPoint(sng.BBox.Min.Sub(svoff)) - ctr := mn.Add(sz.MulScalar(.5)) + sz := sng.BBox.Size() + ctr := sng.BBox.Min.Add(sz.MulScalar(.5)) sn.ApplyDeltaTransform(sv.SVG, del, sc, 0, ctr) } sv.UpdateView(true) @@ -448,7 +444,7 @@ func (gv *Canvas) SelectRaiseTop() { //types:add if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("RaiseTop", es.SelectedNamesString()) sl := es.SelectedList(true) // true = descending = reverse order @@ -471,7 +467,7 @@ func (gv *Canvas) SelectRaise() { //types:add if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("Raise", es.SelectedNamesString()) sl := es.SelectedList(true) // true = descending = reverse order @@ -496,7 +492,7 @@ func (gv *Canvas) SelectLowerBottom() { //types:add if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("LowerBottom", es.SelectedNamesString()) sl := es.SelectedList(true) // true = descending = reverse order @@ -519,7 +515,7 @@ func (gv *Canvas) SelectLower() { //types:add if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("Lower", es.SelectedNamesString()) sl := es.SelectedList(true) // true = descending = reverse order @@ -543,7 +539,7 @@ func (gv *Canvas) SelectSetXPos(xp float32) { if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("MoveToX", fmt.Sprintf("%g", xp)) // todo gv.ChangeMade() @@ -554,7 +550,7 @@ func (gv *Canvas) SelectSetYPos(yp float32) { if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("MoveToY", fmt.Sprintf("%g", yp)) // todo gv.ChangeMade() @@ -565,7 +561,7 @@ func (gv *Canvas) SelectSetWidth(wd float32) { if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("SetWidth", fmt.Sprintf("%g", wd)) // todo gv.ChangeMade() @@ -576,7 +572,7 @@ func (gv *Canvas) SelectSetHeight(ht float32) { if !es.HasSelected() { return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("SetHeight", fmt.Sprintf("%g", ht)) // todo gv.ChangeMade() @@ -586,7 +582,7 @@ func (gv *Canvas) SelectSetHeight(ht float32) { // SelectWithinBBox returns a list of all nodes whose BBox is fully contained // within the given BBox. SVG version excludes layer groups. -func (sv *SVG) SelectWithinBBox(bbox image.Rectangle, leavesOnly bool) []svg.Node { +func (sv *SVG) SelectWithinBBox(bbox math32.Box2, leavesOnly bool) []svg.Node { var rval []svg.Node var curlay tree.Node svg.SVGWalkDownNoDefs(sv.Root(), func(n svg.Node, nb *svg.NodeBase) bool { @@ -615,7 +611,7 @@ func (sv *SVG) SelectWithinBBox(bbox image.Rectangle, leavesOnly bool) []svg.Nod return tree.Break } } - if nb.BBox.In(bbox) { + if bbox.ContainsBox(nb.BBox) { rval = append(rval, n) if curlay == nil && nl != nil { curlay = nl @@ -634,6 +630,7 @@ func (sv *SVG) SelectWithinBBox(bbox image.Rectangle, leavesOnly bool) []svg.Nod // if excludeSel, any leaf nodes that are within the current edit selection are // excluded, func (sv *SVG) SelectContainsPoint(pt image.Point, leavesOnly, excludeSel bool) svg.Node { + ptv := math32.FromPoint(pt) es := sv.EditState() var curlay tree.Node fn := es.FirstSelectedNode() @@ -675,7 +672,7 @@ func (sv *SVG) SelectContainsPoint(pt image.Point, leavesOnly, excludeSel bool) return tree.Break } } - if pt.In(nb.BBox) { + if nb.BBox.ContainsPoint(ptv) { rval = n return tree.Break } diff --git a/canvas/svg.go b/canvas/svg.go index 3ade52ee..1e6b2d3b 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -53,7 +53,7 @@ func (sv *SVG) Init() { sv.SVG.Background = nil sv.Grid = Settings.Size.Grid sv.Styler(func(s *styles.Style) { - s.SetAbilities(true, abilities.Slideable, abilities.Activatable, abilities.Scrollable, abilities.Focusable) + s.SetAbilities(true, abilities.Slideable, abilities.Activatable, abilities.Scrollable, abilities.Focusable, abilities.ScrollableUnattended) s.ObjectFit = styles.FitNone sv.SVG.Root.ViewBox.PreserveAspectRatio.SetFromStyle(s) s.Cursor = cursors.Arrow // todo: modulate based on tool etc @@ -150,9 +150,12 @@ func (sv *SVG) Init() { es.DragStartPos = e.StartPos() // this is the operative start // fmt.Println("sm drag start:", es.DragStartPos) if e.HasAnyModifier(key.Shift) { - del := e.PrevDelta() - sv.SVG.Translate.X += float32(del.X) - sv.SVG.Translate.Y += float32(del.Y) + del := math32.FromPoint(e.PrevDelta()) + if sv.SVG.Scale > 0 { + del.SetDivScalar(min(1, sv.SVG.Scale)) + } + sv.SVG.Translate.SetAdd(del) + sv.UpdateSelSprites() sv.NeedsRender() return } @@ -210,7 +213,13 @@ func (sv *SVG) Init() { e.SetHandled() se := e.(*events.MouseScroll) svoff := sv.Geom.ContentBBox.Min - sv.ZoomAt(se.Pos().Sub(svoff), se.Delta.Y/100) + del := se.Delta.Y / 100 + if sv.SVG.Scale > 0 { + del /= min(1, sv.SVG.Scale) + } + // fmt.Println(sv.SVG.Scale, del) + sv.ZoomAt(se.Pos().Sub(svoff), del) + sv.UpdateSelSprites() sv.NeedsRender() }) } @@ -278,44 +287,6 @@ func (sv *SVG) MouseHover() { } */ -// ContentsBBox returns the object-level box of the entire contents -func (sv *SVG) ContentsBBox() math32.Box2 { - bbox := math32.Box2{} - bbox.SetEmpty() - sv.WalkDown(func(n tree.Node) bool { - if n == sv.This { - return tree.Continue - } - if n == sv.SVG.Defs { - return tree.Break - } - sni, issv := n.(svg.Node) - if !issv { - return tree.Break - } - if NodeIsLayer(n) { - return tree.Continue - } - if txt, istxt := sni.(*svg.Text); istxt { // no tspans - if txt.Text != "" { - return tree.Break - } - } - sn := sni.AsNodeBase() - bb := math32.Box2{} - bb.SetFromRect(sn.BBox) - bbox.ExpandByBox(bb) - if _, isgp := sni.(*svg.Group); isgp { // subsumes all - return tree.Break - } - return tree.Continue - }) - if bbox.IsEmpty() { - bbox = math32.Box2{} - } - return bbox -} - // TransformAllLeaves transforms all the leaf items in the drawing (not groups) // uses ApplyDeltaTransform manipulation. func (sv *SVG) TransformAllLeaves(trans math32.Vector2, scale math32.Vector2, rot float32, pt math32.Vector2) { @@ -346,33 +317,20 @@ func (sv *SVG) TransformAllLeaves(trans math32.Vector2, scale math32.Vector2, ro }) } -// ZoomToPage sets the scale to fit the current viewbox -func (sv *SVG) ZoomToPage(width bool) { - vb := math32.FromPoint(sv.Root().BBox.Size()) - if vb == (math32.Vector2{}) { - return - } - bsz := sv.Root().ViewBox.Size - if bsz.X <= 0 || bsz.Y <= 0 { - return - } - sc := vb.Div(bsz) +func (sv *SVG) ResetZoom() { sv.SVG.Translate.Set(0, 0) - if width { - sv.SVG.Scale = sc.X - } else { - sv.SVG.Scale = math32.Min(sc.X, sc.Y) - } + sv.SVG.Scale = 1 + sv.NeedsRender() } // ZoomToContents sets the scale to fit the current contents into view func (sv *SVG) ZoomToContents(width bool) { - vb := math32.FromPoint(sv.Root().BBox.Size()) + vb := sv.Root().BBox.Size() if vb == (math32.Vector2{}) { return } - sv.ZoomToPage(width) - bb := sv.ContentsBBox() + sv.ResetZoom() + bb := sv.SVG.ContentBounds() bsz := bb.Size() if bsz.X <= 0 || bsz.Y <= 0 { return @@ -393,8 +351,8 @@ func (sv *SVG) ZoomToContents(width bool) { // is preserved -- recommended. func (sv *SVG) ResizeToContents(grid_off bool) { sv.UndoSave("ResizeToContents", "") - sv.ZoomToPage(false) - bb := sv.ContentsBBox() + sv.ResetZoom() + bb := sv.SVG.ContentBounds() bsz := bb.Size() if bsz.X <= 0 || bsz.Y <= 0 { return @@ -405,18 +363,17 @@ func (sv *SVG) ResizeToContents(grid_off bool) { if grid_off { treff.X = math32.Floor(trans.X/incr) * incr treff.Y = math32.Floor(trans.Y/incr) * incr - } - bsz.SetAdd(trans.Sub(treff)) - treff = treff.Negate() - - bsz = bsz.DivScalar(sv.SVG.Scale) - - sv.TransformAllLeaves(treff, math32.Vec2(1, 1), 0, math32.Vec2(0, 0)) - sv.Root().ViewBox.Size = bsz - sv.SVG.PhysicalWidth.Value = bsz.X - sv.SVG.PhysicalHeight.Value = bsz.Y - sv.ZoomToPage(false) + bsz.SetAdd(trans.Sub(treff)) + bsz.X = math32.Ceil(bsz.X/incr) * incr + bsz.Y = math32.Ceil(bsz.Y/incr) * incr + } + root := sv.SVG.Root + root.ViewBox.Min = treff + root.ViewBox.Size = bsz + // sv.SVG.PhysicalWidth.Value = bsz.X + // sv.SVG.PhysicalHeight.Value = bsz.Y sv.Canvas.ChangeMade() + sv.NeedsRender() } // ZoomAt updates the scale and translate parameters at given point @@ -573,17 +530,6 @@ func (sv *SVG) MakeNodeContextMenu(m *core.Scene, kn tree.Node) { SetText("Paste").SetIcon(icons.Paste).SetKey(keymap.Paste) } -// ContextMenuPos returns position to use for context menu, based on input position -func (sv *SVG) NodeContextMenuPos(pos image.Point) image.Point { - if pos != image.ZP { - return pos - } - bbox := sv.Root().BBox - pos.X = (bbox.Min.X + bbox.Max.X) / 2 - pos.Y = (bbox.Min.Y + bbox.Max.Y) / 2 - return pos -} - //////// Undo // UndoSave save current state for potential undo @@ -713,28 +659,28 @@ func (sv *SVG) RenderGrid() { return } sv.UpdateGridEff() + sv.SVG.UpdateBBoxes() // needs this to be updated pc := &sv.Scene.Painter pc.PushContext(&root.Paint, nil) - pc.Stroke.Color = colors.Scheme.Outline + pc.Stroke.Color = colors.Scheme.OutlineVariant pc.Fill.Color = nil sc := sv.SVG.Scale wd := 1 / sc pc.Stroke.Width.Dots = wd - pos := math32.Vec2(0, 0) + pos := root.ViewBox.Min sz := root.ViewBox.Size pc.Rectangle(pos.X, pos.Y, sz.X, sz.Y) if Settings.GridDisp { gsz := float32(sv.GridEff) - pc.Stroke.Color = colors.Scheme.OutlineVariant for x := gsz; x < sz.X; x += gsz { - pc.Line(x, 0, x, sz.Y) + pc.Line(pos.X+x, pos.Y, pos.X+x, pos.Y+sz.Y) } for y := gsz; y < sz.Y; y += gsz { - pc.Line(0, y, sz.X, y) + pc.Line(pos.X, pos.Y+y, pos.X+sz.X, pos.Y+y) } } pc.Draw() diff --git a/canvas/text.go b/canvas/text.go index 0bca96cc..adccb8c0 100644 --- a/canvas/text.go +++ b/canvas/text.go @@ -79,7 +79,7 @@ func (gv *Canvas) SetTextPropertiesNode(sii svg.Node, tps map[string]string) { // SetTextProperties sets the text properties of selected Text nodes func (gv *Canvas) SetTextProperties(tps map[string]string) { es := &gv.EditState - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("SetTextProperties", "") // sv.SetFullReRender() for itm := range es.Selected { @@ -147,7 +147,7 @@ func (gv *Canvas) SetText(txt string) { if len(es.Selected) != 1 { // only if exactly one selected return } - sv := gv.SVG() + sv := gv.SVG sv.UndoSave("SetText", "") // sv.SetFullReRender() for itm := range es.Selected { diff --git a/canvas/tools.go b/canvas/tools.go index 9c4fee51..c845d2e9 100644 --- a/canvas/tools.go +++ b/canvas/tools.go @@ -33,8 +33,8 @@ func ToolDoesBasicSelect(tl Tools) bool { } // SetTool sets the current active tool -func (vc *Canvas) SetTool(tl Tools) { - es := &vc.EditState +func (cv *Canvas) SetTool(tl Tools) { + es := &cv.EditState if es.Tool == tl { return } @@ -53,74 +53,73 @@ func (vc *Canvas) SetTool(tl Tools) { } } es.ResetSelected() - vc.EditState.Tool = tl - vc.SetDefaultStyle() - vc.ModalToolbar().Update() - vc.SetStatus("Tool") - vc.Restyle() - sv := vc.SVG() - sv.UpdateSelect() + cv.EditState.Tool = tl + cv.SetDefaultStyle() + cv.modalTools.Update() + cv.SetStatus("Tool") + cv.Restyle() + cv.SVG.UpdateSelect() } -func (vc *Canvas) MakeTools(p *tree.Plan) { +func (cv *Canvas) MakeTools(p *tree.Plan) { tree.Add(p, func(w *core.Button) { w.SetIcon(icons.ArrowSelectorTool).SetShortcut("S") w.SetTooltip("Select, move, and resize objects") w.OnClick(func(e events.Event) { - vc.SetTool(SelectTool) + cv.SetTool(SelectTool) }) w.Styler(func(s *styles.Style) { - s.SetState(vc.EditState.Tool == SelectTool, states.Selected) + s.SetState(cv.EditState.Tool == SelectTool, states.Selected) }) }) tree.Add(p, func(w *core.Button) { w.SetIcon(cicons.ToolNode).SetShortcut("N") w.SetTooltip("Select and move node points within paths") w.OnClick(func(e events.Event) { - vc.SetTool(NodeTool) + cv.SetTool(NodeTool) }) w.Styler(func(s *styles.Style) { - s.SetState(vc.EditState.Tool == NodeTool, states.Selected) + s.SetState(cv.EditState.Tool == NodeTool, states.Selected) }) }) tree.Add(p, func(w *core.Button) { w.SetIcon(icons.Rectangle).SetShortcut("R") w.SetTooltip("Create rectangles and squares") w.OnClick(func(e events.Event) { - vc.SetTool(RectTool) + cv.SetTool(RectTool) }) w.Styler(func(s *styles.Style) { - s.SetState(vc.EditState.Tool == RectTool, states.Selected) + s.SetState(cv.EditState.Tool == RectTool, states.Selected) }) }) tree.Add(p, func(w *core.Button) { w.SetIcon(icons.Circle).SetShortcut("E") w.SetTooltip("Create circles, ellipses, and arcs") w.OnClick(func(e events.Event) { - vc.SetTool(EllipseTool) + cv.SetTool(EllipseTool) }) w.Styler(func(s *styles.Style) { - s.SetState(vc.EditState.Tool == EllipseTool, states.Selected) + s.SetState(cv.EditState.Tool == EllipseTool, states.Selected) }) }) tree.Add(p, func(w *core.Button) { w.SetIcon(icons.LineCurve).SetShortcut("B") w.SetTooltip("Create bezier curves (straight lines and curves with control points)") w.OnClick(func(e events.Event) { - vc.SetTool(BezierTool) + cv.SetTool(BezierTool) }) w.Styler(func(s *styles.Style) { - s.SetState(vc.EditState.Tool == BezierTool, states.Selected) + s.SetState(cv.EditState.Tool == BezierTool, states.Selected) }) }) tree.Add(p, func(w *core.Button) { w.SetIcon(cicons.ToolText).SetShortcut("T") w.SetTooltip("Add and edit text") w.OnClick(func(e events.Event) { - vc.SetTool(TextTool) + cv.SetTool(TextTool) }) w.Styler(func(s *styles.Style) { - s.SetState(vc.EditState.Tool == TextTool, states.Selected) + s.SetState(cv.EditState.Tool == TextTool, states.Selected) }) }) } diff --git a/canvas/tree.go b/canvas/tree.go index 5cfa0e5a..e951f980 100644 --- a/canvas/tree.go +++ b/canvas/tree.go @@ -25,8 +25,8 @@ type Tree struct { } // SelectNodeInTree selects given node in Tree -func (gv *Canvas) SelectNodeInTree(kn tree.Node, mode events.SelectModes) { - tv := gv.Tree() +func (cv *Canvas) SelectNodeInTree(kn tree.Node, mode events.SelectModes) { + tv := cv.tree tvn := tv.FindSyncNode(kn) if tvn != nil { tvn.OpenParents() @@ -35,13 +35,13 @@ func (gv *Canvas) SelectNodeInTree(kn tree.Node, mode events.SelectModes) { } // SelectedAsTrees returns the currently selected items from SVG as Tree nodes -func (gv *Canvas) SelectedAsTrees() []core.Treer { - es := &gv.EditState +func (cv *Canvas) SelectedAsTrees() []core.Treer { + es := &cv.EditState sl := es.SelectedList(false) if len(sl) == 0 { return nil } - tv := gv.Tree() + tv := cv.tree var tvl []core.Treer for _, si := range sl { tvn := tv.FindSyncNode(si.AsTree().This) @@ -53,70 +53,70 @@ func (gv *Canvas) SelectedAsTrees() []core.Treer { } // DuplicateSelected duplicates selected items in SVG view, using Tree methods -func (gv *Canvas) DuplicateSelected() { //types:add - tvl := gv.SelectedAsTrees() +func (cv *Canvas) DuplicateSelected() { //types:add + tvl := cv.SelectedAsTrees() if len(tvl) == 0 { - gv.SetStatus("Duplicate: no tree items found") + cv.SetStatus("Duplicate: no tree items found") return } - sv := gv.SVG() + sv := cv.SVG sv.UndoSave("DuplicateSelected", "") // sv.SetFullReRender() - tv := gv.Tree() + tv := cv.tree // tv.SetFullReRender() for _, tr := range tvl { tr.AsCoreTree().Duplicate() } - gv.SetStatus("Duplicated selected items") + cv.SetStatus("Duplicated selected items") tv.Resync() // todo: should not be needed - gv.ChangeMade() + cv.ChangeMade() } // CopySelected copies selected items in SVG view, using Tree methods -func (gv *Canvas) CopySelected() { //types:add - tvl := gv.SelectedAsTrees() +func (cv *Canvas) CopySelected() { //types:add + tvl := cv.SelectedAsTrees() if len(tvl) == 0 { - gv.SetStatus("Copy: no tree items found") + cv.SetStatus("Copy: no tree items found") return } - tv := gv.Tree() + tv := cv.tree tv.SetSelectedNodes(tvl) tvl[0].Copy() // operates on first element in selection - gv.SetStatus("Copied selected items") + cv.SetStatus("Copied selected items") } // CutSelected cuts selected items in SVG view, using Tree methods -func (gv *Canvas) CutSelected() { //types:add - tvl := gv.SelectedAsTrees() +func (cv *Canvas) CutSelected() { //types:add + tvl := cv.SelectedAsTrees() if len(tvl) == 0 { - gv.SetStatus("Cut: no tree items found") + cv.SetStatus("Cut: no tree items found") return } - sv := gv.SVG() + sv := cv.SVG sv.UndoSave("CutSelected", "") // sv.SetFullReRender() sv.EditState().ResetSelected() - tv := gv.Tree() + tv := cv.tree // tv.SetFullReRender() tv.SetSelectedNodes(tvl) tvl[0].Cut() // operates on first element in selection - gv.SetStatus("Cut selected items") + cv.SetStatus("Cut selected items") tv.Resync() // todo: should not be needed sv.UpdateSelSprites() - gv.ChangeMade() + cv.ChangeMade() } // PasteClip pastes clipboard, using cur layer etc -func (gv *Canvas) PasteClip() { //types:add - md := gv.Clipboard().Read([]string{fileinfo.DataJson}) +func (cv *Canvas) PasteClip() { //types:add + md := cv.Clipboard().Read([]string{fileinfo.DataJson}) if md == nil { return } // es := &gv.EditState - sv := gv.SVG() + sv := cv.SVG sv.UndoSave("Paste", "") // sv.SetFullReRender() - tv := gv.Tree() + tv := cv.tree // tv.SetFullReRender() // parent := tv // if es.CurLayer != "" { @@ -126,31 +126,31 @@ func (gv *Canvas) PasteClip() { //types:add // } // } // par.PasteChildren(md, dnd.DropCopy) - gv.SetStatus("Pasted items from clipboard") + cv.SetStatus("Pasted items from clipboard") tv.Resync() // todo: should not be needed - gv.ChangeMade() + cv.ChangeMade() } // DeleteSelected deletes selected items in SVG view, using Tree methods -func (gv *Canvas) DeleteSelected() { - tvl := gv.SelectedAsTrees() +func (cv *Canvas) DeleteSelected() { + tvl := cv.SelectedAsTrees() if len(tvl) == 0 { - gv.SetStatus("Delete: no tree items found") + cv.SetStatus("Delete: no tree items found") return } - sv := gv.SVG() + sv := cv.SVG sv.UndoSave("DeleteSelected", "") sv.EditState().ResetSelected() // sv.SetFullReRender() - tv := gv.Tree() + tv := cv.tree // tv.SetFullReRender() // for _, tvi := range tvl { // tvi.SrcDelete() // } - gv.SetStatus("Deleted selected items") + cv.SetStatus("Deleted selected items") tv.Resync() // todo: should not be needed sv.UpdateSelSprites() - gv.ChangeMade() + cv.ChangeMade() } /////////////////////////////////////////////// @@ -215,17 +215,17 @@ func (tv *Tree) Init() { // SelectSVG func (tv *Tree) SelectSVG() { - gv := tv.Canvas - if gv != nil { - gv.SelectNodeInSVG(tv.SyncNode, events.SelectOne) + cv := tv.Canvas + if cv != nil { + cv.SelectNodeInSVG(tv.SyncNode, events.SelectOne) } } // LayerIsCurrent returns true if layer is the current active one for creating func (tv *Tree) LayerIsCurrent() bool { - gv := tv.Canvas - if gv != nil { - return gv.IsCurLayer(tv.SyncNode.AsTree().Name) + cv := tv.Canvas + if cv != nil { + return cv.IsCurLayer(tv.SyncNode.AsTree().Name) } return false } @@ -233,9 +233,9 @@ func (tv *Tree) LayerIsCurrent() bool { // LayerSetCurrent sets this layer as the current layer name func (tv *Tree) LayerSetCurrent() { sn := tv.SyncNode - gv := tv.Canvas - if gv != nil { - cur := gv.EditState.CurLayer + cv := tv.Canvas + if cv != nil { + cur := cv.EditState.CurLayer if cur != "" { cli := tv.Parent.AsTree().ChildByName("tv_"+cur, 0) if cli != nil { @@ -249,7 +249,7 @@ func (tv *Tree) LayerSetCurrent() { if !LayerIsVisible(sn) { tv.LayerToggleVis() } - gv.SetCurLayer(sn.AsTree().Name) + cv.SetCurLayer(sn.AsTree().Name) // tv.SetFullReRender() // needed for icon updating // tv.UpdateSig() } @@ -257,9 +257,9 @@ func (tv *Tree) LayerSetCurrent() { // LayerClearCurrent clears this layer as the current layer if it was set as such. func (tv *Tree) LayerClearCurrent() { - gv := tv.Canvas - if gv != nil { - gv.ClearCurLayer(tv.SyncNode.AsTree().Name) + cv := tv.Canvas + if cv != nil { + cv.ClearCurLayer(tv.SyncNode.AsTree().Name) // tv.SetFullReRender() // needed for icon updating // tv.UpdateSig() } From 5a10a26ec5b096e02018c426aa4b29f17560708b Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 19 May 2025 18:38:34 -0700 Subject: [PATCH 5/8] canvas: ZoomAt is now 100% good. it still diverges outside of center but this is inevitable -- test is whether center works. zoom to contents mostly working --- canvas/canvas.go | 2 +- canvas/svg.go | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/canvas/canvas.go b/canvas/canvas.go index e4f8293e..3f7a3969 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -216,7 +216,7 @@ func (cv *Canvas) OpenDrawingFile(fnm core.Filename) error { cv.EditState.Gradients = sv.Gradients() sv.SVG.GatherIDs() // also ensures uniqueness, key for json saving - sv.ZoomToContents(false) + sv.ResetZoom() sv.ReadMetaData() return err } diff --git a/canvas/svg.go b/canvas/svg.go index 1e6b2d3b..b6088caf 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -212,13 +212,12 @@ func (sv *SVG) Init() { sv.On(events.Scroll, func(e events.Event) { e.SetHandled() se := e.(*events.MouseScroll) - svoff := sv.Geom.ContentBBox.Min del := se.Delta.Y / 100 if sv.SVG.Scale > 0 { del /= min(1, sv.SVG.Scale) } // fmt.Println(sv.SVG.Scale, del) - sv.ZoomAt(se.Pos().Sub(svoff), del) + sv.ZoomAt(se.Pos(), del) sv.UpdateSelSprites() sv.NeedsRender() }) @@ -325,24 +324,21 @@ func (sv *SVG) ResetZoom() { // ZoomToContents sets the scale to fit the current contents into view func (sv *SVG) ZoomToContents(width bool) { - vb := sv.Root().BBox.Size() - if vb == (math32.Vector2{}) { - return - } sv.ResetZoom() bb := sv.SVG.ContentBounds() bsz := bb.Size() - if bsz.X <= 0 || bsz.Y <= 0 { + if bsz == (math32.Vector2{}) { return } - sc := vb.Div(bsz) - sv.SVG.Translate = bb.Min.DivScalar(sv.SVG.Scale).Negate() + vsz := sv.Geom.Size.Actual.Content + sc := vsz.Div(bsz) + sv.SVG.Translate = bb.Min.Negate() if width { - sv.SVG.Scale *= sc.X + sv.SVG.Scale = sc.X } else { - sv.SVG.Scale *= math32.Min(sc.X, sc.Y) + sv.SVG.Scale = math32.Min(sc.X, sc.Y) } - sv.UpdateView(true) + sv.NeedsRender() } // ResizeToContents resizes the drawing to just fit the current contents, @@ -387,14 +383,19 @@ func (sv *SVG) ZoomAt(pt image.Point, delta float32) { sc *= (1 - math32.Min(-delta, .5)) } - nsc := sv.SVG.Scale * sc + osc := sv.SVG.Scale + nsc := osc * sc - mpt := math32.FromPoint(pt) - lpt := mpt.DivScalar(sv.SVG.Scale).Sub(sv.SVG.Translate) // point in drawing coords + root := sv.SVG.Root + rxf := root.Paint.Transform + xf := rxf.Inverse() - dt := lpt.Add(sv.SVG.Translate).MulScalar((nsc - sv.SVG.Scale) / nsc) // delta from zooming - sv.SVG.Translate.SetSub(dt) + mpt := math32.FromPoint(pt) + xpt := xf.MulVector2AsPoint(mpt) + xpt.SetSub(root.ViewBox.Min) + dt := xpt.DivScalar(nsc).Sub(xpt.DivScalar(osc)) + sv.SVG.Translate.SetAdd(dt) sv.SVG.Scale = nsc } From 09393a0bc997c2b7452c17e74f3607459a698474 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 19 May 2025 18:50:38 -0700 Subject: [PATCH 6/8] canvas: align fixed, and sel points updated in UpdateView --- canvas/align.go | 20 ++++++++++++-------- canvas/canvas.go | 3 ++- canvas/svg.go | 4 +--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/canvas/align.go b/canvas/align.go index 1210dc80..446c6fd5 100644 --- a/canvas/align.go +++ b/canvas/align.go @@ -131,12 +131,11 @@ func (sv *SVG) alignAnchorBBox(aa AlignAnchors) (math32.Box2, svg.Node) { } // alignImpl does alignment -func (sv *SVG) alignImpl(apos math32.Vector2, an svg.Node, dim math32.Dims, act string) { +func (sv *SVG) alignImpl(apos math32.Vector2, an svg.Node, useMax bool, dim math32.Dims, act string) { es := sv.EditState() if !es.HasSelected() { return } - // svoff := sv.Root().BBox.Min sv.UndoSave(act, es.SelectedNamesString()) sc := math32.Vec2(1, 1) odim := math32.OtherDim(dim) @@ -145,8 +144,13 @@ func (sv *SVG) alignImpl(apos math32.Vector2, an svg.Node, dim math32.Dims, act continue } sng := sn.AsNodeBase() - bb := sng.BBox // .Sub(svoff) - del := apos.Sub(bb.Min) + bb := sng.BBox + var del math32.Vector2 + if useMax { + del = apos.Sub(bb.Max) + } else { + del = apos.Sub(bb.Min) + } del.SetDim(odim, 0) sn.ApplyDeltaTransform(sv.SVG, del, sc, 0, bb.Min) } @@ -158,7 +162,7 @@ func (sv *SVG) AlignMin(aa AlignAnchors, dim math32.Dims, act string) { return } abb, an := sv.alignAnchorBBox(aa) - sv.alignImpl(abb.Min, an, dim, act) + sv.alignImpl(abb.Min, an, false, dim, act) } func (sv *SVG) AlignMinAnchor(aa AlignAnchors, dim math32.Dims, act string) { @@ -166,7 +170,7 @@ func (sv *SVG) AlignMinAnchor(aa AlignAnchors, dim math32.Dims, act string) { return } abb, an := sv.alignAnchorBBox(aa) - sv.alignImpl(abb.Max, an, dim, act) + sv.alignImpl(abb.Max, an, false, dim, act) } func (sv *SVG) AlignMax(aa AlignAnchors, dim math32.Dims, act string) { @@ -174,7 +178,7 @@ func (sv *SVG) AlignMax(aa AlignAnchors, dim math32.Dims, act string) { return } abb, an := sv.alignAnchorBBox(aa) - sv.alignImpl(abb.Max, an, dim, act) + sv.alignImpl(abb.Max, an, true, dim, act) } func (sv *SVG) AlignMaxAnchor(aa AlignAnchors, dim math32.Dims, act string) { @@ -182,7 +186,7 @@ func (sv *SVG) AlignMaxAnchor(aa AlignAnchors, dim math32.Dims, act string) { return } abb, an := sv.alignAnchorBBox(aa) - sv.alignImpl(abb.Min, an, dim, act) + sv.alignImpl(abb.Min, an, true, dim, act) } func (sv *SVG) AlignCenter(aa AlignAnchors, dim math32.Dims, act string) { diff --git a/canvas/canvas.go b/canvas/canvas.go index 3f7a3969..991e31fb 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -490,7 +490,7 @@ func (cv *Canvas) MakeToolbar(p *tree.Plan) { w.OnClick(func(e events.Event) { sv := cv.SVG sv.ResetZoom() - sv.UpdateView(true) + sv.UpdateView(false) }) }) tree.Add(p, func(w *core.Button) { @@ -499,6 +499,7 @@ func (cv *Canvas) MakeToolbar(p *tree.Plan) { w.OnClick(func(e events.Event) { sv := cv.SVG sv.ZoomToContents(false) + sv.UpdateView(false) }) }) } diff --git a/canvas/svg.go b/canvas/svg.go index b6088caf..7cfc1546 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -254,6 +254,7 @@ func (sv *SVG) EditState() *EditState { // UpdateView updates the view, optionally with a full re-render func (sv *SVG) UpdateView(full bool) { // TODO(config) + sv.SVG.UpdateBBoxes() // needs this to be updated sv.UpdateSelSprites() sv.NeedsRender() } @@ -319,7 +320,6 @@ func (sv *SVG) TransformAllLeaves(trans math32.Vector2, scale math32.Vector2, ro func (sv *SVG) ResetZoom() { sv.SVG.Translate.Set(0, 0) sv.SVG.Scale = 1 - sv.NeedsRender() } // ZoomToContents sets the scale to fit the current contents into view @@ -338,7 +338,6 @@ func (sv *SVG) ZoomToContents(width bool) { } else { sv.SVG.Scale = math32.Min(sc.X, sc.Y) } - sv.NeedsRender() } // ResizeToContents resizes the drawing to just fit the current contents, @@ -660,7 +659,6 @@ func (sv *SVG) RenderGrid() { return } sv.UpdateGridEff() - sv.SVG.UpdateBBoxes() // needs this to be updated pc := &sv.Scene.Painter pc.PushContext(&root.Paint, nil) From 8db50ff504ab9a61db86e0f3a35dda12cfeaba47 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Tue, 20 May 2025 01:57:56 -0700 Subject: [PATCH 7/8] canvas: bidirectional PaintSetter updating and setting minimally working --- canvas/canvas.go | 53 +++--- canvas/paint.go | 462 ++++++++++++++++++++-------------------------- canvas/shapes.go | 2 +- canvas/svg.go | 84 +-------- canvas/text.go | 4 +- canvas/typegen.go | 39 ++-- 6 files changed, 257 insertions(+), 387 deletions(-) diff --git a/canvas/canvas.go b/canvas/canvas.go index 991e31fb..2496e1ac 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -141,7 +141,7 @@ func (cv *Canvas) Init() { cv.tabs = w w.SetType(core.FunctionalTabs) pt, _ := w.NewTab("Paint") - NewPaintView(pt).SetCanvas(cv) + NewPaintSetter(pt).SetCanvas(cv) at, _ := w.NewTab("Align") NewAlignView(at).SetCanvas(cv) cv.EditState.Text.Defaults() @@ -216,7 +216,7 @@ func (cv *Canvas) OpenDrawingFile(fnm core.Filename) error { cv.EditState.Gradients = sv.Gradients() sv.SVG.GatherIDs() // also ensures uniqueness, key for json saving - sv.ResetZoom() + sv.SVG.ZoomReset() sv.ReadMetaData() return err } @@ -273,7 +273,7 @@ func (cv *Canvas) SetPhysSize(sz *PhysSize) { sv := cv.SVG sz.SetToSVG(sv) sv.SetMetaData() - sv.ResetZoom() + sv.SVG.ZoomReset() } // SaveDrawing saves .svg drawing to current filename @@ -489,7 +489,7 @@ func (cv *Canvas) MakeToolbar(p *tree.Plan) { w.SetTooltip("Zoom to see the entire page size for drawing") w.OnClick(func(e events.Event) { sv := cv.SVG - sv.ResetZoom() + sv.SVG.ZoomReset() sv.UpdateView(false) }) }) @@ -498,7 +498,7 @@ func (cv *Canvas) MakeToolbar(p *tree.Plan) { w.SetTooltip("Zoom to see all elements") w.OnClick(func(e events.Event) { sv := cv.SVG - sv.ZoomToContents(false) + sv.SVG.ZoomToContents(sv.Geom.Size.Actual.Content) sv.UpdateView(false) }) }) @@ -583,8 +583,8 @@ func (gv *Canvas) Tab(name string) *core.Frame { return gv.tabs.TabByName(name) } -func (cv *Canvas) PaintView() *PaintView { - return cv.Tab("Paint").Child(0).(*PaintView) +func (cv *Canvas) PaintSetter() *PaintSetter { + return cv.Tab("Paint").Child(0).(*PaintSetter) } // UpdateAll updates the display @@ -605,7 +605,7 @@ func (cv *Canvas) UpdateTree() { } func (cv *Canvas) SetDefaultStyle() { - // pv := vv.PaintView() + // pv := vv.PaintSetter() // es := &vv.EditState // switch es.Tool { // case TextTool: @@ -618,24 +618,25 @@ func (cv *Canvas) SetDefaultStyle() { } func (cv *Canvas) UpdateTabs() { - // es := &vv.EditState - // fsel := es.FirstSelectedNode() - // if fsel != nil { - // sel := fsel.AsNodeBase() - // pv := vv.PaintView() - // pv.Update(&sel.Paint, sel.This) - // txt, istxt := fsel.(*svg.Text) - // if istxt { - // es.Text.SetFromNode(txt) - // txv := vv.Tab("Text").(*core.Form) - // txv.UpdateFields() - // // todo: only show text toolbar on double-click - // // gv.SetModalText() - // // gv.UpdateTextToolbar() - // } else { - // vv.SetModalToolbar() - // } - // } + es := &cv.EditState + fsel := es.FirstSelectedNode() + if fsel == nil { + return + } + sel := fsel.AsNodeBase() + pv := cv.PaintSetter() + pv.UpdateFromNode(&sel.Paint, sel) + txt, istxt := fsel.(*svg.Text) + if istxt { + es.Text.SetFromNode(txt) + txv := cv.Tab("Text") + txv.Update() + // todo: only show text toolbar on double-click + // gv.SetModalText() + // gv.UpdateTextToolbar() + } else { + // cv.SetModalToolbar() + } } // SelectNodeInSVG selects given svg node in SVG drawing diff --git a/canvas/paint.go b/canvas/paint.go index 80890628..76bef491 100644 --- a/canvas/paint.go +++ b/canvas/paint.go @@ -6,9 +6,12 @@ package canvas import ( "fmt" + "image" + "strings" "cogentcore.org/core/base/reflectx" "cogentcore.org/core/colors" + "cogentcore.org/core/colors/gradient" "cogentcore.org/core/core" "cogentcore.org/core/events" "cogentcore.org/core/styles" @@ -16,9 +19,9 @@ import ( "cogentcore.org/core/tree" ) -// PaintView provides editing of basic Stroke and Fill painting parameters +// PaintSetter provides setting of basic Stroke and Fill painting parameters // for selected items -type PaintView struct { +type PaintSetter struct { core.Frame // Active styles @@ -41,9 +44,12 @@ type PaintView struct { curStrokeType PaintTypes curFillType PaintTypes + + strokeStack *core.Frame + fillStack *core.Frame } -func (pv *PaintView) Init() { +func (pv *PaintSetter) Init() { pv.Frame.Init() pv.StrokeType = PaintSolid pv.FillType = PaintSolid @@ -95,7 +101,7 @@ func (pv *PaintView) Init() { w.SetMin(0).SetStep(0.05) w.OnChange(func(e events.Event) { if pv.IsStrokeOn() { - pv.Canvas.SetStrokeWidth(pv.StrokeWidthProp(), false) + pv.Canvas.SetStrokeWidth(pv.StrokeWidthProp()) } }) }) @@ -105,7 +111,7 @@ func (pv *PaintView) Init() { core.Bind(&pv.PaintStyle.Stroke.Width.Unit, w) w.OnChange(func(e events.Event) { if pv.IsStrokeOn() { - pv.Canvas.SetStrokeWidth(pv.StrokeWidthProp(), false) + pv.Canvas.SetStrokeWidth(pv.StrokeWidthProp()) } }) }) @@ -191,10 +197,10 @@ func (pv *PaintView) Init() { }) }) - //////////////////////////////// - // stroke stack + //////// stroke stack tree.AddChildAt(pv, "stroke-stack", func(w *core.Frame) { + pv.strokeStack = w w.StackTop = 1 // ss.StackTopOnly = true w.Styler(func(s *styles.Style) { @@ -259,6 +265,7 @@ func (pv *PaintView) Init() { }) tree.AddChildAt(pv, "fill-stack", func(w *core.Frame) { + pv.fillStack = w w.StackTop = 1 // fs.StackTopOnly = true w.Styler(func(s *styles.Style) { @@ -312,15 +319,13 @@ func (pv *PaintView) Init() { }) } -///////////////////////////////////////////////////////////////////////// -// Actions +//////// Actions -// ManipAction manages all the updating etc associated with performing an action -// that includes an ongoing manipulation with a final non-manip update. -// runs given function to actually do the update. -func (vv *Canvas) ManipAction(act Actions, data string, manip bool, fun func(sii svg.Node)) { - es := &vv.EditState - sv := vv.SVG +// PaintSet manages all the updating etc associated with setting paint +// parameters. +func (cv *Canvas) PaintSet(act Actions, data string, manip bool, fun func(nd svg.Node)) { + es := &cv.EditState + sv := cv.SVG // update := false actStart := false finalAct := false @@ -337,216 +342,195 @@ func (vv *Canvas) ManipAction(act Actions, data string, manip bool, fun func(sii if !finalAct { sv.UndoSave(act.String(), data) } - // update = sv.UpdateStart() } for itm := range es.Selected { - vv.ManipActionFun(itm, fun) + cv.PaintSetFun(itm, fun) } if !manip { - // sv.UpdateEnd(update) if !actStart { es.ActDone() - vv.ChangeMade() + cv.ChangeMade() } } else { sv.NeedsRender() } } -func (vv *Canvas) ManipActionFun(sii svg.Node, fun func(itm svg.Node)) { - if gp, isgp := sii.(*svg.Group); isgp { +func (cv *Canvas) PaintSetFun(nd svg.Node, fun func(itm svg.Node)) { + if gp, isgp := nd.(*svg.Group); isgp { for _, kid := range gp.Children { - vv.ManipActionFun(kid.(svg.Node), fun) + cv.PaintSetFun(kid.(svg.Node), fun) } return } - fun(sii) + fun(nd) +} + +// SetPaintPropNode sets paint property on given node, +// using given setter function. +func SetPaintPropNode(nd svg.Node, fun func(g svg.Node)) { + if gp, isgp := nd.(*svg.Group); isgp { + for _, kid := range gp.Children { + SetPaintPropNode(kid.(svg.Node), fun) + } + return + } + fun(nd) +} + +// SetPaintProp sets paint property selected nodes, +// using given setter function. +func (cv *Canvas) SetPaintProp(actName, val string, fun func(g svg.Node)) { + es := &cv.EditState + cv.SVG.UndoSave(actName, val) + for itm := range es.Selected { + SetPaintPropNode(itm, fun) + } + cv.ChangeMade() } // SetColorNode sets the color properties of Node // based on previous and current PaintType -func (vv *Canvas) SetColorNode(sii svg.Node, prop string, prev, pt PaintTypes, sp string) { - if gp, isgp := sii.(*svg.Group); isgp { +func (cv *Canvas) SetColorNode(nd svg.Node, prop string, prev, pt PaintTypes, sp string) { + if gp, isgp := nd.(*svg.Group); isgp { for _, kid := range gp.Children { - vv.SetColorNode(kid.(svg.Node), prop, prev, pt, sp) + cv.SetColorNode(kid.(svg.Node), prop, prev, pt, sp) } return } switch pt { // case PaintLinear: - // svg.UpdateNodeGradientProp(sii, prop, false, sp) + // svg.UpdateNodeGradientProp(nd, prop, false, sp) // case PaintRadial: - // svg.UpdateNodeGradientProp(sii, prop, true, sp) + // svg.UpdateNodeGradientProp(nd, prop, true, sp) default: if prev == PaintLinear || prev == PaintRadial { - pstr := reflectx.ToString(sii.AsTree().Properties[prop]) + pstr := reflectx.ToString(nd.AsTree().Properties[prop]) _ = pstr - // svg.DeleteNodeGradient(sii, pstr) + // svg.DeleteNodeGradient(nd, pstr) } - sii.AsNodeBase().SetColorProperties(prop, sp) + nd.AsNodeBase().SetColorProperties(prop, sp) } - vv.UpdateMarkerColors(sii) + cv.UpdateMarkerColors(nd) } // SetStroke sets the stroke properties of selected items // based on previous and current PaintType -func (vv *Canvas) SetStroke(prev, pt PaintTypes, sp string) { - es := &vv.EditState - sv := vv.SVG - sv.UndoSave("SetStroke", sp) - for itm := range es.Selected { - vv.SetColorNode(itm, "stroke", prev, pt, sp) - } - vv.ChangeMade() -} - -// SetStrokeWidthNode sets the stroke width of Node -func (vv *Canvas) SetStrokeWidthNode(sii svg.Node, wp string) { - if gp, isgp := sii.(*svg.Group); isgp { - for _, kid := range gp.Children { - vv.SetStrokeWidthNode(kid.(svg.Node), wp) - } - return - } - g := sii.AsNodeBase() - if g.Paint.Stroke.Color != nil { - g.SetProperty("stroke-width", wp) - } -} - -// SetStrokeWidth sets the stroke width property for selected items -// manip means currently being manipulated -- don't save undo. -func (vv *Canvas) SetStrokeWidth(wp string, manip bool) { - es := &vv.EditState - sv := vv.SVG - if !manip { - sv.UndoSave("SetStrokeWidth", wp) - } - for itm := range es.Selected { - vv.SetStrokeWidthNode(itm, wp) - } - if !manip { - vv.ChangeMade() - } else { - sv.NeedsRender() - } +func (cv *Canvas) SetStroke(prev, pt PaintTypes, sp string) { + cv.SetPaintProp("SetStroke", sp, func(nd svg.Node) { + cv.SetColorNode(nd, "stroke", prev, pt, sp) + }) } // SetStrokeColor sets the stroke color for selected items. // manip means currently being manipulated -- don't save undo. -func (vv *Canvas) SetStrokeColor(sp string, manip bool) { - vv.ManipAction(SetStrokeColor, sp, manip, +func (cv *Canvas) SetStrokeColor(sp string, manip bool) { + cv.PaintSet(SetStrokeColor, sp, manip, func(itm svg.Node) { p := itm.AsTree().Properties["stroke"] if p != nil { itm.AsNodeBase().SetColorProperties("stroke", sp) - vv.UpdateMarkerColors(itm) + cv.UpdateMarkerColors(itm) } }) } -// SetMarkerNode sets the marker properties of Node. -func (vv *Canvas) SetMarkerNode(sii svg.Node, start, mid, end string, sc, mc, ec MarkerColors) { - if gp, isgp := sii.(*svg.Group); isgp { - for _, kid := range gp.Children { - vv.SetMarkerNode(kid.(svg.Node), start, mid, end, sc, mc, ec) +func (cv *Canvas) SetStrokeWidth(wp string) { + cv.SetPaintProp("SetStrokeWidth", wp, func(nd svg.Node) { + g := nd.AsNodeBase() + if g.Paint.Stroke.Color != nil { + g.SetProperty("stroke-width", wp) } - return - } - sv := vv.SVG - MarkerSetProp(sv.SVG, sii, "marker-start", start, sc) - MarkerSetProp(sv.SVG, sii, "marker-mid", mid, mc) - MarkerSetProp(sv.SVG, sii, "marker-end", end, ec) + }) } // SetMarkerProperties sets the marker properties -func (vv *Canvas) SetMarkerProperties(start, mid, end string, sc, mc, ec MarkerColors) { - es := &vv.EditState - sv := vv.SVG - sv.UndoSave("SetMarkerProperties", start+" "+mid+" "+end) - for itm := range es.Selected { - vv.SetMarkerNode(itm, start, mid, end, sc, mc, ec) - } - vv.ChangeMade() +func (cv *Canvas) SetMarkerProperties(start, mid, end string, sc, mc, ec MarkerColors) { + sv := cv.SVG.SVG + cv.SetPaintProp("SetMarkerProperties", start+" "+mid+" "+end, func(nd svg.Node) { + MarkerSetProp(sv, nd, "marker-start", start, sc) + MarkerSetProp(sv, nd, "marker-mid", mid, mc) + MarkerSetProp(sv, nd, "marker-end", end, ec) + }) } // UpdateMarkerColors updates the marker colors, when setting fill or stroke -func (vv *Canvas) UpdateMarkerColors(sii svg.Node) { - if sii == nil { +func (cv *Canvas) UpdateMarkerColors(nd svg.Node) { + if nd == nil { return } - sv := vv.SVG - MarkerUpdateColorProp(sv.SVG, sii, "marker-start") - MarkerUpdateColorProp(sv.SVG, sii, "marker-mid") - MarkerUpdateColorProp(sv.SVG, sii, "marker-end") + sv := cv.SVG + MarkerUpdateColorProp(sv.SVG, nd, "marker-start") + MarkerUpdateColorProp(sv.SVG, nd, "marker-mid") + MarkerUpdateColorProp(sv.SVG, nd, "marker-end") } // SetDashNode sets the stroke-dasharray property of Node. // multiplies dash values by the line width in dots. -func (vv *Canvas) SetDashNode(sii svg.Node, dary []float64) { - if gp, isgp := sii.(*svg.Group); isgp { +func (cv *Canvas) SetDashNode(nd svg.Node, dary []float64) { + if gp, isgp := nd.(*svg.Group); isgp { for _, kid := range gp.Children { - vv.SetDashNode(kid.(svg.Node), dary) + cv.SetDashNode(kid.(svg.Node), dary) } return } if len(dary) == 0 { - delete(sii.AsTree().Properties, "stroke-dasharray") + delete(nd.AsTree().Properties, "stroke-dasharray") return } - g := sii.AsNodeBase() + g := nd.AsNodeBase() mary := DashMulWidth(float64(g.Paint.Stroke.Width.Dots), dary) ds := DashString(mary) - sii.AsTree().Properties["stroke-dasharray"] = ds + nd.AsTree().Properties["stroke-dasharray"] = ds } // SetDashProperties sets the dash properties -func (vv *Canvas) SetDashProperties(dary []float64) { - es := &vv.EditState - sv := vv.SVG +func (cv *Canvas) SetDashProperties(dary []float64) { + es := &cv.EditState + sv := cv.SVG sv.UndoSave("SetDashProperties", "") // update := sv.UpdateStart() // sv.SetFullReRender() for itm := range es.Selected { - vv.SetDashNode(itm, dary) + cv.SetDashNode(itm, dary) } // sv.UpdateEnd(update) - vv.ChangeMade() + cv.ChangeMade() } // SetFill sets the fill properties of selected items // based on previous and current PaintType -func (vv *Canvas) SetFill(prev, pt PaintTypes, fp string) { - es := &vv.EditState - sv := vv.SVG +func (cv *Canvas) SetFill(prev, pt PaintTypes, fp string) { + es := &cv.EditState + sv := cv.SVG sv.UndoSave("SetFill", fp) // update := sv.UpdateStart() // sv.SetFullReRender() for itm := range es.Selected { - vv.SetColorNode(itm, "fill", prev, pt, fp) + cv.SetColorNode(itm, "fill", prev, pt, fp) } // sv.UpdateEnd(update) - vv.ChangeMade() + cv.ChangeMade() } // SetFillColor sets the fill color for selected items // manip means currently being manipulated -- don't save undo. -func (vv *Canvas) SetFillColor(fp string, manip bool) { - vv.ManipAction(SetFillColor, fp, manip, +func (cv *Canvas) SetFillColor(fp string, manip bool) { + cv.PaintSet(SetFillColor, fp, manip, func(itm svg.Node) { p := itm.AsTree().Properties["fill"] if p != nil { itm.AsNodeBase().SetColorProperties("fill", fp) - vv.UpdateMarkerColors(itm) + cv.UpdateMarkerColors(itm) } }) } // DefaultGradient returns the default gradient to use for setting stops -func (vv *Canvas) DefaultGradient() string { - es := &vv.EditState - sv := vv.SVG - if len(vv.EditState.Gradients) == 0 { +func (cv *Canvas) DefaultGradient() string { + es := &cv.EditState + sv := cv.SVG + if len(cv.EditState.Gradients) == 0 { es.ConfigDefaultGradient() sv.UpdateGradients(es.Gradients) } @@ -554,177 +538,128 @@ func (vv *Canvas) DefaultGradient() string { } // UpdateGradients updates gradients from EditState -func (vv *Canvas) UpdateGradients() { - es := &vv.EditState - sv := vv.SVG +func (cv *Canvas) UpdateGradients() { + es := &cv.EditState + sv := cv.SVG // update := sv.UpdateStart() sv.UpdateGradients(es.Gradients) // sv.UpdateEnd(update) } -/////////////////////////////////////////////////////////////// -// PaintView +//////// PaintSetter // Update updates the current settings based on the values in the given Paint and // properties from node (node can be nil) -/* -func (pv *PaintView) Update(pc *paint.Paint, kn tree.Node) { - update := pv.UpdateStart() - defer pv.UpdateEnd(update) - - pv.StrokeType, pv.StrokeStops = pv.DecodeType(kn, &pc.Stroke.Color, "stroke") - pv.FillType, pv.FillStops = pv.DecodeType(kn, &pc.Fill.Color, "fill") +func (pv *PaintSetter) UpdateFromNode(ps *styles.Paint, nd svg.Node) { + pv.StrokeType, pv.StrokeStops = pv.GetPaintType(nd, ps.Stroke.Color, "stroke") + pv.FillType, pv.FillStops = pv.GetPaintType(nd, ps.Fill.Color, "fill") - es := &pv.Vector.EditState - grl := &es.Gradients - - spt := pv.ChildByName("stroke-lab", 0).ChildByName("stroke-type", 1).(*core.ButtonBox) - spt.SelectItem(int(pv.StrokeType)) - - ss := pv.StrokeStack() + // es := &pv.Canvas.EditState + // grl := &es.Gradients switch pv.StrokeType { case PaintSolid: - if ss.StackTop != 1 { - ss.SetFullReRender() - } - ss.StackTop = 1 - sc := ss.ChildByName("stroke-clr", 1).(*core.ColorPicker) - sc.SetColor(pc.Stroke.Color.Color) + pv.strokeStack.StackTop = 1 + pv.PaintStyle.Stroke.Color = ps.Stroke.Color case PaintLinear, PaintRadial: - if ss.StackTop != 2 { - ss.SetFullReRender() - } - ss.StackTop = 2 - sg := ss.ChildByName("stroke-grad", 1).(*core.Table) - sg.SetSlice(grl) - pv.SelectStrokeGrad() + pv.strokeStack.StackTop = 2 + // sg := ss.ChildByName("stroke-grad", 1).(*core.Table) + // sg.SetSlice(grl) + // pv.SelectStrokeGrad() default: - if ss.StackTop != 0 { - ss.SetFullReRender() - } - ss.StackTop = 0 - } - - wr := pv.ChildByName("stroke-width", 2) - wsb := wr.ChildByName("width", 1).(*core.Spinner) - wsb.SetValue(pc.Stroke.Width.Val) - uncb := wr.ChildByName("width-units", 2).(*core.Chooser) - uncb.SetCurrentIndex(int(pc.Stroke.Width.Un)) - - dshcb := wr.ChildByName("dashes", 3).(*core.Chooser) - nwdsh, dnm := DashMatchArray(float64(pc.Stroke.Width.Dots), pc.Stroke.Dashes) - if nwdsh { - dshcb.ItemsFromIconList(AllDashIcons, false, 0) + pv.strokeStack.StackTop = 0 } - dshcb.SetCurVal(icons.Icon(dnm)) - mkr := pv.ChildByName("stroke-markers", 3) + pv.PaintStyle.Stroke.Width = ps.Stroke.Width + pv.PaintStyle.Stroke.Dashes = ps.Stroke.Dashes - ms, _, mc := MarkerFromNodeProp(kn, "marker-start") - mscb := mkr.ChildByName("marker-start", 0).(*core.Chooser) - mscc := mkr.ChildByName("marker-start-color", 1).(*core.Chooser) - if ms != "" { - mscb.SetCurVal(MarkerNameToIcon(ms)) - mscc.SetCurrentIndex(int(mc)) - } else { - mscb.SetCurrentIndex(0) - mscc.SetCurrentIndex(0) - } - ms, _, mc = MarkerFromNodeProp(kn, "marker-mid") - mmcb := mkr.ChildByName("marker-mid", 2).(*core.Chooser) - mmcc := mkr.ChildByName("marker-mid-color", 3).(*core.Chooser) - if ms != "" { - mmcb.SetCurVal(MarkerNameToIcon(ms)) - mmcc.SetCurrentIndex(int(mc)) - } else { - mmcb.SetCurrentIndex(0) - mmcc.SetCurrentIndex(0) - } - ms, _, mc = MarkerFromNodeProp(kn, "marker-end") - mecb := mkr.ChildByName("marker-end", 4).(*core.Chooser) - mecc := mkr.ChildByName("marker-end-color", 5).(*core.Chooser) - if ms != "" { - mecb.SetCurVal(MarkerNameToIcon(ms)) - mecc.SetCurrentIndex(int(mc)) - } else { - mecb.SetCurrentIndex(0) - mecc.SetCurrentIndex(0) - } - - fpt := pv.ChildByName("fill-lab", 0).ChildByName("fill-type", 1).(*core.ButtonBox) - fpt.SelectItem(int(pv.FillType)) - - fs := pv.FillStack() + // ms, _, mc := MarkerFromNodeProp(nd, "marker-start") + // mscb := mkr.ChildByName("marker-start", 0).(*core.Chooser) + // mscc := mkr.ChildByName("marker-start-color", 1).(*core.Chooser) + // if ms != "" { + // mscb.SetCurVal(MarkerNameToIcon(ms)) + // mscc.SetCurrentIndex(int(mc)) + // } else { + // mscb.SetCurrentIndex(0) + // mscc.SetCurrentIndex(0) + // } + // ms, _, mc = MarkerFromNodeProp(nd, "marker-mid") + // mmcb := mkr.ChildByName("marker-mid", 2).(*core.Chooser) + // mmcc := mkr.ChildByName("marker-mid-color", 3).(*core.Chooser) + // if ms != "" { + // mmcb.SetCurVal(MarkerNameToIcon(ms)) + // mmcc.SetCurrentIndex(int(mc)) + // } else { + // mmcb.SetCurrentIndex(0) + // mmcc.SetCurrentIndex(0) + // } + // ms, _, mc = MarkerFromNodeProp(nd, "marker-end") + // mecb := mkr.ChildByName("marker-end", 4).(*core.Chooser) + // mecc := mkr.ChildByName("marker-end-color", 5).(*core.Chooser) + // if ms != "" { + // mecb.SetCurVal(MarkerNameToIcon(ms)) + // mecc.SetCurrentIndex(int(mc)) + // } else { + // mecb.SetCurrentIndex(0) + // mecc.SetCurrentIndex(0) + // } switch pv.FillType { case PaintSolid: - if fs.StackTop != 1 { - fs.SetFullReRender() - } - fs.StackTop = 1 - fc := fs.ChildByName("fill-clr", 1).(*core.ColorPicker) - fc.SetColor(pc.Fill.Color.Color) + pv.fillStack.StackTop = 1 + pv.PaintStyle.Fill.Color = ps.Fill.Color case PaintLinear, PaintRadial: - if fs.StackTop != 2 { - fs.SetFullReRender() - } - fs.StackTop = 2 - fg := fs.ChildByName("fill-grad", 1).(*core.Table) - if fg.Slice != grl { - pv.SetFullReRender() - } - fg.SetSlice(grl) - pv.SelectFillGrad() + pv.fillStack.StackTop = 2 + // fg := fs.ChildByName("fill-grad", 1).(*core.Table) + // if fg.Slice != grl { + // pv.SetFullReRender() + // } + // fg.SetSlice(grl) + // pv.SelectFillGrad() default: - if fs.StackTop != 0 { - fs.SetFullReRender() - } - fs.StackTop = 0 + pv.fillStack.StackTop = 0 } + pv.Update() } -*/ -/* // GradStopsName returns the stopsname for gradient from url -func (pv *PaintView) GradStopsName(gii core.Node2D, url string) string { - gr := svg.GradientByName(gii, url) +func (pv *PaintSetter) GradStopsName(nd svg.Node, url string) string { + gr := pv.Canvas.SSVG().GradientByName(nd, url) if gr == nil { return "" } if gr.StopsName != "" { return gr.StopsName } - return gr.Nm + return gr.Name } -*/ -/* -// DecodeType decodes the paint type from paint and properties +// GetPaintType decodes the paint type from paint and properties // also returns the name of the gradient if using one. -func (pv *PaintView) DecodeType(kn tree.Node, cs *style.ColorSpec, prop string) (PaintTypes, string) { +func (pv *PaintSetter) GetPaintType(nd svg.Node, clr image.Image, prop string) (PaintTypes, string) { pstr := "" - var gii core.Node2D - if kn != nil { - pstr = reflectx.ToString(kn.Prop(prop)) - gii = kn.(core.Node2D) + if nd != nil { + pv := nd.AsNodeBase().Properties[prop] + pstr = reflectx.ToString(pv) } ptyp := PaintSolid grnm := "" + lg, islg := clr.(*gradient.Linear) + rg, isrg := clr.(*gradient.Radial) switch { case pstr == "inherit": ptyp = PaintInherit - case pstr == "none" || cs.IsNil(): + case pstr == "none" || clr == nil: ptyp = PaintOff - case strings.HasPrefix(pstr, "url(#linear") || (cs.Gradient != nil && !cs.Gradient.IsRadial): + case strings.HasPrefix(pstr, "url(#linear") || (islg && lg != nil): ptyp = PaintLinear - if gii != nil { - grnm = pv.GradStopsName(gii, pstr) + if nd != nil { + grnm = pv.GradStopsName(nd, pstr) } - case strings.HasPrefix(pstr, "url(#radial") || (cs.Gradient != nil && cs.Gradient.IsRadial): + case strings.HasPrefix(pstr, "url(#radial") || (isrg && rg != nil): ptyp = PaintRadial - if gii != nil { - grnm = pv.GradStopsName(gii, pstr) + if nd != nil { + grnm = pv.GradStopsName(nd, pstr) } default: ptyp = PaintSolid @@ -738,9 +673,8 @@ func (pv *PaintView) DecodeType(kn tree.Node, cs *style.ColorSpec, prop string) } return ptyp, grnm } -*/ -func (pv *PaintView) SelectStrokeGrad() { +func (pv *PaintSetter) SelectStrokeGrad() { // todo: // es := &pv.Vector.EditState // grl := &es.Gradients @@ -755,7 +689,7 @@ func (pv *PaintView) SelectStrokeGrad() { // } } -func (pv *PaintView) SelectFillGrad() { +func (pv *PaintSetter) SelectFillGrad() { // todo: // es := &pv.Vector.EditState // grl := &es.Gradients @@ -771,7 +705,7 @@ func (pv *PaintView) SelectFillGrad() { } // StrokeProp returns the stroke property string according to current settings -func (pv *PaintView) StrokeProp() string { +func (pv *PaintSetter) StrokeProp() string { // ss := pv.StrokeStack() switch pv.StrokeType { case PaintOff: @@ -790,7 +724,7 @@ func (pv *PaintView) StrokeProp() string { // MarkerProp returns the marker property string according to current settings // along with color type to set. -func (pv *PaintView) MarkerProperties() (start, mid, end string, sc, mc, ec MarkerColors) { +func (pv *PaintSetter) MarkerProperties() (start, mid, end string, sc, mc, ec MarkerColors) { // mkr := pv.ChildByName("stroke-markers", 3) // // mscb := mkr.ChildByName("marker-start", 0).(*core.Chooser) @@ -812,19 +746,19 @@ func (pv *PaintView) MarkerProperties() (start, mid, end string, sc, mc, ec Mark } // IsStrokeOn returns true if stroke is active -func (pv *PaintView) IsStrokeOn() bool { +func (pv *PaintSetter) IsStrokeOn() bool { return pv.StrokeType >= PaintSolid && pv.StrokeType < PaintInherit } // StrokeWidthProp returns stroke-width property -func (pv *PaintView) StrokeWidthProp() string { +func (pv *PaintSetter) StrokeWidthProp() string { unnm := pv.PaintStyle.Stroke.Width.Unit.String() return fmt.Sprintf("%g%s", pv.PaintStyle.Stroke.Width.Value, unnm) } // StrokeDashProp returns stroke-dasharray property as an array (nil = none) // these values need to be multiplied by line widths for each item. -func (pv *PaintView) StrokeDashProp() []float64 { +func (pv *PaintSetter) StrokeDashProp() []float64 { // todo: need type for dashes // wr := pv.ChildByName("stroke-width", 2) // dshcb := wr.AsTree().ChildByName("dashes", 3).(*core.Chooser) @@ -844,12 +778,12 @@ func (pv *PaintView) StrokeDashProp() []float64 { } // IsFillOn returns true if Fill is active -func (pv *PaintView) IsFillOn() bool { +func (pv *PaintSetter) IsFillOn() bool { return pv.FillType >= PaintSolid && pv.FillType < PaintInherit } // FillProp returns the fill property string according to current settings -func (pv *PaintView) FillProp() string { +func (pv *PaintSetter) FillProp() string { switch pv.FillType { case PaintOff: return "none" @@ -866,14 +800,14 @@ func (pv *PaintView) FillProp() string { } // SetProperties sets the properties for given node according to current settings -func (pv *PaintView) SetProperties(sii svg.Node) { - pv.Canvas.SetColorNode(sii, "stroke", pv.StrokeType, pv.StrokeType, pv.StrokeProp()) +func (pv *PaintSetter) SetProperties(nd svg.Node) { + cv := pv.Canvas + cv.SetColorNode(nd, "stroke", pv.StrokeType, pv.StrokeType, pv.StrokeProp()) if pv.IsStrokeOn() { - sii.AsTree().Properties["stroke-width"] = pv.StrokeWidthProp() - start, mid, end, sc, mc, ec := pv.MarkerProperties() - pv.Canvas.SetMarkerNode(sii, start, mid, end, sc, mc, ec) + nd.AsTree().Properties["stroke-width"] = pv.StrokeWidthProp() + cv.SetMarkerProperties(pv.MarkerProperties()) } - pv.Canvas.SetColorNode(sii, "fill", pv.FillType, pv.FillType, pv.FillProp()) + cv.SetColorNode(nd, "fill", pv.FillType, pv.FillType, pv.FillProp()) } type PaintTypes int32 //enums:enum -trim-prefix Paint diff --git a/canvas/shapes.go b/canvas/shapes.go index f948e2c1..25970b25 100644 --- a/canvas/shapes.go +++ b/canvas/shapes.go @@ -36,7 +36,7 @@ func NewSVGElement[T tree.NodeValue](sv *SVG) *T { n := tree.New[T](parent) sn := any(n).(svg.Node) sv.SetSVGName(sn) - sv.Canvas.PaintView().SetProperties(sn) + sv.Canvas.PaintSetter().SetProperties(sn) sv.Canvas.UpdateTree() return n } diff --git a/canvas/svg.go b/canvas/svg.go index 7cfc1546..a6cf9832 100644 --- a/canvas/svg.go +++ b/canvas/svg.go @@ -7,7 +7,6 @@ package canvas import ( "bytes" "fmt" - "image" "strings" "cogentcore.org/core/base/errors" @@ -216,8 +215,7 @@ func (sv *SVG) Init() { if sv.SVG.Scale > 0 { del /= min(1, sv.SVG.Scale) } - // fmt.Println(sv.SVG.Scale, del) - sv.ZoomAt(se.Pos(), del) + sv.SVG.ZoomAt(se.Pos(), del) sv.UpdateSelSprites() sv.NeedsRender() }) @@ -317,87 +315,21 @@ func (sv *SVG) TransformAllLeaves(trans math32.Vector2, scale math32.Vector2, ro }) } -func (sv *SVG) ResetZoom() { - sv.SVG.Translate.Set(0, 0) - sv.SVG.Scale = 1 -} - -// ZoomToContents sets the scale to fit the current contents into view -func (sv *SVG) ZoomToContents(width bool) { - sv.ResetZoom() - bb := sv.SVG.ContentBounds() - bsz := bb.Size() - if bsz == (math32.Vector2{}) { - return - } - vsz := sv.Geom.Size.Actual.Content - sc := vsz.Div(bsz) - sv.SVG.Translate = bb.Min.Negate() - if width { - sv.SVG.Scale = sc.X - } else { - sv.SVG.Scale = math32.Min(sc.X, sc.Y) - } -} - // ResizeToContents resizes the drawing to just fit the current contents, // including moving everything to start at upper-left corner, -// optionally preserving the current grid offset, so grid snapping -// is preserved -- recommended. -func (sv *SVG) ResizeToContents(grid_off bool) { +// optionally preserving the current grid sizing, so grid snapping +// is preserved, which is recommended. +func (sv *SVG) ResizeToContents(gridIncr bool) { sv.UndoSave("ResizeToContents", "") - sv.ResetZoom() - bb := sv.SVG.ContentBounds() - bsz := bb.Size() - if bsz.X <= 0 || bsz.Y <= 0 { - return + grid := float32(1) + if gridIncr { + grid = sv.Grid } - trans := bb.Min - incr := sv.Grid * sv.SVG.Scale // our zoom factor - treff := trans - if grid_off { - treff.X = math32.Floor(trans.X/incr) * incr - treff.Y = math32.Floor(trans.Y/incr) * incr - bsz.SetAdd(trans.Sub(treff)) - bsz.X = math32.Ceil(bsz.X/incr) * incr - bsz.Y = math32.Ceil(bsz.Y/incr) * incr - } - root := sv.SVG.Root - root.ViewBox.Min = treff - root.ViewBox.Size = bsz - // sv.SVG.PhysicalWidth.Value = bsz.X - // sv.SVG.PhysicalHeight.Value = bsz.Y + sv.SVG.ResizeToContents(grid) sv.Canvas.ChangeMade() sv.NeedsRender() } -// ZoomAt updates the scale and translate parameters at given point -// by given delta: + means zoom in, - means zoom out, -// delta should always be < 1) -func (sv *SVG) ZoomAt(pt image.Point, delta float32) { - sc := float32(1) - if delta > 1 { - sc += delta - } else { - sc *= (1 - math32.Min(-delta, .5)) - } - - osc := sv.SVG.Scale - nsc := osc * sc - - root := sv.SVG.Root - rxf := root.Paint.Transform - xf := rxf.Inverse() - - mpt := math32.FromPoint(pt) - xpt := xf.MulVector2AsPoint(mpt) - xpt.SetSub(root.ViewBox.Min) - dt := xpt.DivScalar(nsc).Sub(xpt.DivScalar(osc)) - - sv.SVG.Translate.SetAdd(dt) - sv.SVG.Scale = nsc -} - // MetaData returns the overall metadata and grid if present. // if mknew is true, it will create new ones if not found. func (sv *SVG) MetaData(mknew bool) (main, grid *svg.MetaData) { diff --git a/canvas/text.go b/canvas/text.go index adccb8c0..bd1cec37 100644 --- a/canvas/text.go +++ b/canvas/text.go @@ -17,10 +17,10 @@ type TextStyle struct { Text string // FontStyle styling properties. - FontStyle styles.Font `new-window:"+"` + FontStyle styles.Font `display:"add-fields"` // TextStyle styling properties. - TextStyle styles.Text `new-window:"+"` + TextStyle styles.Text `display:"add-fields"` // the parent [Canvas] Canvas *Canvas `copier:"-" json:"-" xml:"-" display:"-"` diff --git a/canvas/typegen.go b/canvas/typegen.go index f605998d..fd32cdb7 100644 --- a/canvas/typegen.go +++ b/canvas/typegen.go @@ -22,48 +22,51 @@ func (t *AlignView) SetAnchor(v AlignAnchors) *AlignView { t.Anchor = v; return // the parent [Canvas] func (t *AlignView) SetCanvas(v *Canvas) *AlignView { t.Canvas = v; return t } -var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.Canvas", IDName: "canvas", Doc: "Canvas is the main widget of the Cogent Canvas SVG vector graphics program.", Methods: []types.Method{{Name: "OpenDrawing", Doc: "OpenDrawing opens a new .svg drawing", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fnm"}, Returns: []string{"error"}}, {Name: "PromptPhysSize", Doc: "PromptPhysSize prompts for the physical size of the drawing and sets it", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SaveDrawing", Doc: "SaveDrawing saves .svg drawing to current filename", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Returns: []string{"error"}}, {Name: "SaveDrawingAs", Doc: "SaveDrawingAs saves .svg drawing to given filename", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}, Returns: []string{"error"}}, {Name: "ExportPNG", Doc: "ExportPNG exports drawing to a PNG image (auto-names to same name\nwith .png suffix). Calls inkscape -- needs to be on the PATH.\nspecify either width or height of resulting image, or nothing for\nphysical size as set. Renders full current page -- do ResizeToContents\nto render just current contents.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"width", "height"}, Returns: []string{"error"}}, {Name: "ExportPDF", Doc: "ExportPDF exports drawing to a PDF file (auto-names to same name\nwith .pdf suffix). Calls inkscape -- needs to be on the PATH.\nspecify DPI of resulting image for effects rendering.\nRenders full current page -- do ResizeToContents\nto render just current contents.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"dpi"}, Returns: []string{"error"}}, {Name: "ResizeToContents", Doc: "ResizeToContents resizes the drawing to just fit the current contents,\nincluding moving everything to start at upper-left corner,\npreserving the current grid offset, so grid snapping\nis preserved.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "AddImage", Doc: "AddImage adds a new image node set to the given image", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname", "width", "height"}, Returns: []string{"error"}}, {Name: "UpdateAll", Doc: "UpdateAll updates the display", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Undo", Doc: "Undo undoes the last action", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Returns: []string{"string"}}, {Name: "Redo", Doc: "Redo redoes the previously undone action", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Returns: []string{"string"}}, {Name: "AddLayer", Doc: "AddLayer adds a new layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectGroup", Doc: "SelectGroup groups items together", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectUnGroup", Doc: "SelectUnGroup ungroups items from each other", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRotateLeft", Doc: "SelectRotateLeft rotates the selection 90 degrees counter-clockwise", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRotateRight", Doc: "SelectRotateRight rotates the selection 90 degrees clockwise", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectFlipHorizontal", Doc: "SelectFlipHorizontal flips the selection horizontally", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectFlipVertical", Doc: "SelectFlipVertical flips the selection vertically", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRaiseTop", Doc: "SelectRaiseTop raises the selection to the top of the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRaise", Doc: "SelectRaise raises the selection by one level in the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectLowerBottom", Doc: "SelectLowerBottom lowers the selection to the bottom of the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectLower", Doc: "SelectLower lowers the selection by one level in the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "DuplicateSelected", Doc: "DuplicateSelected duplicates selected items in SVG view, using Tree methods", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "CopySelected", Doc: "CopySelected copies selected items in SVG view, using Tree methods", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "CutSelected", Doc: "CutSelected cuts selected items in SVG view, using Tree methods", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "PasteClip", Doc: "PasteClip pastes clipboard, using cur layer etc", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "Frame"}}, Fields: []types.Field{{Name: "Filename", Doc: "full path to current drawing filename"}, {Name: "EditState", Doc: "current edit state"}}}) +var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.Canvas", IDName: "canvas", Doc: "Canvas is the main widget of the Cogent Canvas SVG vector graphics program.", Methods: []types.Method{{Name: "OpenDrawing", Doc: "OpenDrawing opens a new .svg drawing", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fnm"}, Returns: []string{"error"}}, {Name: "PromptPhysSize", Doc: "PromptPhysSize prompts for the physical size of the drawing and sets it", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SaveDrawing", Doc: "SaveDrawing saves .svg drawing to current filename", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Returns: []string{"error"}}, {Name: "SaveDrawingAs", Doc: "SaveDrawingAs saves .svg drawing to given filename", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname"}, Returns: []string{"error"}}, {Name: "ExportPNG", Doc: "ExportPNG exports drawing to a PNG image (auto-names to same name\nwith .png suffix). Calls inkscape -- needs to be on the PATH.\nspecify either width or height of resulting image, or nothing for\nphysical size as set. Renders full current page -- do ResizeToContents\nto render just current contents.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"width", "height"}, Returns: []string{"error"}}, {Name: "ExportPDF", Doc: "ExportPDF exports drawing to a PDF file (auto-names to same name\nwith .pdf suffix). Calls inkscape -- needs to be on the PATH.\nspecify DPI of resulting image for effects rendering.\nRenders full current page -- do ResizeToContents\nto render just current contents.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"dpi"}, Returns: []string{"error"}}, {Name: "ResizeToContents", Doc: "ResizeToContents resizes the drawing to just fit the current contents,\nincluding moving everything to start at upper-left corner,\npreserving the current grid offset, so grid snapping\nis preserved.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "AddImage", Doc: "AddImage adds a new image node set to the given image", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"fname", "width", "height"}, Returns: []string{"error"}}, {Name: "UpdateAll", Doc: "UpdateAll updates the display", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "Undo", Doc: "Undo undoes the last action", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Returns: []string{"string"}}, {Name: "Redo", Doc: "Redo redoes the previously undone action", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Returns: []string{"string"}}, {Name: "AddLayer", Doc: "AddLayer adds a new layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectGroup", Doc: "SelectGroup groups items together", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectUnGroup", Doc: "SelectUnGroup ungroups items from each other", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRotateLeft", Doc: "SelectRotateLeft rotates the selection 90 degrees counter-clockwise", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRotateRight", Doc: "SelectRotateRight rotates the selection 90 degrees clockwise", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectFlipHorizontal", Doc: "SelectFlipHorizontal flips the selection horizontally", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectFlipVertical", Doc: "SelectFlipVertical flips the selection vertically", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRaiseTop", Doc: "SelectRaiseTop raises the selection to the top of the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectRaise", Doc: "SelectRaise raises the selection by one level in the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectLowerBottom", Doc: "SelectLowerBottom lowers the selection to the bottom of the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "SelectLower", Doc: "SelectLower lowers the selection by one level in the layer", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "DuplicateSelected", Doc: "DuplicateSelected duplicates selected items in SVG view, using Tree methods", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "CopySelected", Doc: "CopySelected copies selected items in SVG view, using Tree methods", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "CutSelected", Doc: "CutSelected cuts selected items in SVG view, using Tree methods", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}, {Name: "PasteClip", Doc: "PasteClip pastes clipboard, using cur layer etc", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "Frame"}}, Fields: []types.Field{{Name: "Filename", Doc: "full path to current drawing filename"}, {Name: "EditState", Doc: "current edit state"}, {Name: "SVG"}, {Name: "tabs"}, {Name: "splits"}, {Name: "modalTools"}, {Name: "tools"}, {Name: "tree"}, {Name: "layerTree"}, {Name: "layers"}, {Name: "statusBar"}}}) // NewCanvas returns a new [Canvas] with the given optional parent: // Canvas is the main widget of the Cogent Canvas SVG vector graphics program. func NewCanvas(parent ...tree.Node) *Canvas { return tree.New[Canvas](parent...) } -var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.PaintView", IDName: "paint-view", Doc: "PaintView provides editing of basic Stroke and Fill painting parameters\nfor selected items", Embeds: []types.Field{{Name: "Frame"}}, Fields: []types.Field{{Name: "PaintStyle", Doc: "Active styles"}, {Name: "StrokeType", Doc: "paint type for stroke"}, {Name: "StrokeStops", Doc: "name of gradient with stops"}, {Name: "FillType", Doc: "paint type for fill"}, {Name: "FillStops", Doc: "name of gradient with stops"}, {Name: "Canvas", Doc: "the parent [Canvas]"}, {Name: "curStrokeType"}, {Name: "curFillType"}}}) +// SetSVG sets the [Canvas.SVG] +func (t *Canvas) SetSVG(v *SVG) *Canvas { t.SVG = v; return t } -// NewPaintView returns a new [PaintView] with the given optional parent: -// PaintView provides editing of basic Stroke and Fill painting parameters +var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.PaintSetter", IDName: "paint-setter", Doc: "PaintSetter provides setting of basic Stroke and Fill painting parameters\nfor selected items", Embeds: []types.Field{{Name: "Frame"}}, Fields: []types.Field{{Name: "PaintStyle", Doc: "Active styles"}, {Name: "StrokeType", Doc: "paint type for stroke"}, {Name: "StrokeStops", Doc: "name of gradient with stops"}, {Name: "FillType", Doc: "paint type for fill"}, {Name: "FillStops", Doc: "name of gradient with stops"}, {Name: "Canvas", Doc: "the parent [Canvas]"}, {Name: "curStrokeType"}, {Name: "curFillType"}}}) + +// NewPaintSetter returns a new [PaintSetter] with the given optional parent: +// PaintSetter provides setting of basic Stroke and Fill painting parameters // for selected items -func NewPaintView(parent ...tree.Node) *PaintView { return tree.New[PaintView](parent...) } +func NewPaintSetter(parent ...tree.Node) *PaintSetter { return tree.New[PaintSetter](parent...) } -// SetPaintStyle sets the [PaintView.PaintStyle]: +// SetPaintStyle sets the [PaintSetter.PaintStyle]: // Active styles -func (t *PaintView) SetPaintStyle(v styles.Paint) *PaintView { t.PaintStyle = v; return t } +func (t *PaintSetter) SetPaintStyle(v styles.Paint) *PaintSetter { t.PaintStyle = v; return t } -// SetStrokeType sets the [PaintView.StrokeType]: +// SetStrokeType sets the [PaintSetter.StrokeType]: // paint type for stroke -func (t *PaintView) SetStrokeType(v PaintTypes) *PaintView { t.StrokeType = v; return t } +func (t *PaintSetter) SetStrokeType(v PaintTypes) *PaintSetter { t.StrokeType = v; return t } -// SetStrokeStops sets the [PaintView.StrokeStops]: +// SetStrokeStops sets the [PaintSetter.StrokeStops]: // name of gradient with stops -func (t *PaintView) SetStrokeStops(v string) *PaintView { t.StrokeStops = v; return t } +func (t *PaintSetter) SetStrokeStops(v string) *PaintSetter { t.StrokeStops = v; return t } -// SetFillType sets the [PaintView.FillType]: +// SetFillType sets the [PaintSetter.FillType]: // paint type for fill -func (t *PaintView) SetFillType(v PaintTypes) *PaintView { t.FillType = v; return t } +func (t *PaintSetter) SetFillType(v PaintTypes) *PaintSetter { t.FillType = v; return t } -// SetFillStops sets the [PaintView.FillStops]: +// SetFillStops sets the [PaintSetter.FillStops]: // name of gradient with stops -func (t *PaintView) SetFillStops(v string) *PaintView { t.FillStops = v; return t } +func (t *PaintSetter) SetFillStops(v string) *PaintSetter { t.FillStops = v; return t } -// SetCanvas sets the [PaintView.Canvas]: +// SetCanvas sets the [PaintSetter.Canvas]: // the parent [Canvas] -func (t *PaintView) SetCanvas(v *Canvas) *PaintView { t.Canvas = v; return t } +func (t *PaintSetter) SetCanvas(v *Canvas) *PaintSetter { t.Canvas = v; return t } var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.PhysSize", IDName: "phys-size", Doc: "PhysSize specifies the physical size of the drawing, when making a new one", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Fields: []types.Field{{Name: "StandardSize", Doc: "select a standard size -- this will set units and size"}, {Name: "Portrait", Doc: "for standard size, use first number as width, second as height"}, {Name: "Units", Doc: "default units to use, e.g., in line widths etc"}, {Name: "Size", Doc: "drawing size, in Units"}, {Name: "Grid", Doc: "grid spacing, in units of ViewBox size"}}}) var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.SettingsData", IDName: "settings-data", Doc: "SettingsData is the overall Vector settings", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Methods: []types.Method{{Name: "Apply", Doc: "Apply settings updates things according with settings", Directives: []types.Directive{{Tool: "types", Directive: "add"}}}}, Embeds: []types.Field{{Name: "SettingsBase"}}, Fields: []types.Field{{Name: "Size", Doc: "default physical size, when app is started without opening a file"}, {Name: "ShapeStyle", Doc: "default shape styles"}, {Name: "TextStyle", Doc: "default text styles"}, {Name: "PathStyle", Doc: "default line styles"}, {Name: "LineStyle", Doc: "default line styles"}, {Name: "GridDisp", Doc: "turns on the grid display"}, {Name: "SnapGrid", Doc: "snap positions and sizes to underlying grid"}, {Name: "SnapGuide", Doc: "snap positions and sizes to line up with other elements"}, {Name: "SnapNodes", Doc: "snap node movements to align with guides"}, {Name: "SnapTol", Doc: "number of screen pixels around target point (in either direction) to snap"}, {Name: "SplitName", Doc: "named-split config in use for configuring the splitters"}, {Name: "EnvVars", Doc: "environment variables to set for this app -- if run from the command line, standard shell environment variables are inherited, but on some OS's (Mac), they are not set when run as a gui app"}}}) -var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.SVG", IDName: "svg", Doc: "SVG is the element for viewing and interacting with the SVG.", Methods: []types.Method{{Name: "EditNode", Doc: "EditNode opens a [core.Form] dialog on the given node.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"n"}}}, Embeds: []types.Field{{Name: "WidgetBase"}}, Fields: []types.Field{{Name: "SVG", Doc: "SVG is the SVG drawing to display in this widget"}, {Name: "Canvas", Doc: "the parent [Canvas]"}, {Name: "Grid", Doc: "grid spacing, in native ViewBox units"}, {Name: "GridEff", Doc: "effective grid spacing given Scale level"}, {Name: "pixelMu", Doc: "pixelMu is a mutex protecting the updating of curPixels\nfrom svg render"}, {Name: "currentPixels", Doc: "currentPixels are the current rendered pixels, with the SVG\non top of the background pixel grid. updated in separate\ngoroutine, protected by pixelMu, to ensure fluid interaction"}, {Name: "backgroundPixels", Doc: "background pixels, includes page outline and grid"}, {Name: "backgroundPaint", Doc: "background paint rendering context"}, {Name: "inRender", Doc: "in svg Rendering"}, {Name: "backgroundSize", Doc: "size of bg image rendered"}, {Name: "backgroundTransform", Doc: "bg rendered transform"}, {Name: "backgroundGridEff", Doc: "bg rendered grid"}}}) +var _ = types.AddType(&types.Type{Name: "cogentcore.org/cogent/canvas.SVG", IDName: "svg", Doc: "SVG is the element for viewing and interacting with the SVG.", Methods: []types.Method{{Name: "EditNode", Doc: "EditNode opens a [core.Form] dialog on the given node.", Directives: []types.Directive{{Tool: "types", Directive: "add"}}, Args: []string{"n"}}}, Embeds: []types.Field{{Name: "WidgetBase"}}, Fields: []types.Field{{Name: "SVG", Doc: "SVG is the SVG drawing to display in this widget"}, {Name: "Canvas", Doc: "the parent [Canvas]"}, {Name: "Grid", Doc: "grid spacing, in native ViewBox units"}, {Name: "GridEff", Doc: "effective grid spacing given Scale level"}, {Name: "backgroundGridEff", Doc: "bg rendered grid"}}}) // NewSVG returns a new [SVG] with the given optional parent: // SVG is the element for viewing and interacting with the SVG. From ae8131c752c275aab0a828459e531ef5494f1e90 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Tue, 20 May 2025 11:29:13 -0700 Subject: [PATCH 8/8] canvas: updated to latest color picker behavior and Input vs. Change event tracking etc. all working well finally. --- canvas/paint.go | 111 +++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/canvas/paint.go b/canvas/paint.go index 76bef491..b6e18b65 100644 --- a/canvas/paint.go +++ b/canvas/paint.go @@ -100,6 +100,7 @@ func (pv *PaintSetter) Init() { core.Bind(&pv.PaintStyle.Stroke.Width.Value, w) w.SetMin(0).SetStep(0.05) w.OnChange(func(e events.Event) { + pv.PaintStyle.ToDots() if pv.IsStrokeOn() { pv.Canvas.SetStrokeWidth(pv.StrokeWidthProp()) } @@ -214,9 +215,14 @@ func (pv *PaintSetter) Init() { tree.AddChild(w, func(w *core.ColorPicker) { core.Bind(&pv.PaintStyle.Stroke.Color, w) + w.OnInput(func(e events.Event) { + if pv.StrokeType == PaintSolid { + pv.Canvas.SetStrokeColor(pv.StrokeProp(), false) // not final + } + }) w.OnChange(func(e events.Event) { if pv.StrokeType == PaintSolid { - pv.Canvas.SetStrokeColor(pv.StrokeProp(), false) // not manip + pv.Canvas.SetStrokeColor(pv.StrokeProp(), true) // final } }) }) @@ -279,9 +285,14 @@ func (pv *PaintSetter) Init() { tree.AddChild(w, func(w *core.ColorPicker) { core.Bind(&pv.PaintStyle.Fill.Color, w) + w.OnInput(func(e events.Event) { + if pv.FillType == PaintSolid { + pv.Canvas.SetFillColor(pv.FillProp(), false) // not final + } + }) w.OnChange(func(e events.Event) { if pv.FillType == PaintSolid { - pv.Canvas.SetFillColor(pv.FillProp(), false) // not manip + pv.Canvas.SetFillColor(pv.FillProp(), true) // final } }) }) @@ -319,74 +330,67 @@ func (pv *PaintSetter) Init() { }) } -//////// Actions +//////// setPaintProp -// PaintSet manages all the updating etc associated with setting paint -// parameters. -func (cv *Canvas) PaintSet(act Actions, data string, manip bool, fun func(nd svg.Node)) { +// setPaintPropNode sets a paint property on given node, +// using given setter function. +func setPaintPropNode(nd svg.Node, fun func(g svg.Node)) { + if gp, isgp := nd.(*svg.Group); isgp { + for _, kid := range gp.Children { + setPaintPropNode(kid.(svg.Node), fun) + } + return + } + fun(nd) +} + +// setPaintPropInput sets paint property from a slider-based input that +// sends continuous [events.Input] events, followed by a final [events.Change] +// event, which should have the final = true flag set. This uses the +// [Action] framework to manage the undo saving dynamics involved. +func (cv *Canvas) setPaintPropInput(act Actions, data string, final bool, fun func(nd svg.Node)) { es := &cv.EditState sv := cv.SVG - // update := false actStart := false finalAct := false - if !manip && es.InAction() { + if final && es.InAction() { finalAct = true } - if manip && !es.InAction() { - manip = false + if !final && !es.InAction() { + final = true actStart = true es.ActStart(act, data) es.ActUnlock() } - if !manip { - if !finalAct { + if final { + if !finalAct { // was already saved earlier otherwise sv.UndoSave(act.String(), data) } } - for itm := range es.Selected { - cv.PaintSetFun(itm, fun) + for nd := range es.Selected { + setPaintPropNode(nd, fun) } - if !manip { + if final { if !actStart { es.ActDone() cv.ChangeMade() + sv.NeedsRender() } } else { sv.NeedsRender() } } -func (cv *Canvas) PaintSetFun(nd svg.Node, fun func(itm svg.Node)) { - if gp, isgp := nd.(*svg.Group); isgp { - for _, kid := range gp.Children { - cv.PaintSetFun(kid.(svg.Node), fun) - } - return - } - fun(nd) -} - -// SetPaintPropNode sets paint property on given node, +// setPaintProp sets paint property on selected nodes, // using given setter function. -func SetPaintPropNode(nd svg.Node, fun func(g svg.Node)) { - if gp, isgp := nd.(*svg.Group); isgp { - for _, kid := range gp.Children { - SetPaintPropNode(kid.(svg.Node), fun) - } - return - } - fun(nd) -} - -// SetPaintProp sets paint property selected nodes, -// using given setter function. -func (cv *Canvas) SetPaintProp(actName, val string, fun func(g svg.Node)) { +func (cv *Canvas) setPaintProp(actName, val string, fun func(g svg.Node)) { es := &cv.EditState cv.SVG.UndoSave(actName, val) for itm := range es.Selected { - SetPaintPropNode(itm, fun) + setPaintPropNode(itm, fun) } cv.ChangeMade() + cv.SVG.NeedsRender() } // SetColorNode sets the color properties of Node @@ -417,15 +421,16 @@ func (cv *Canvas) SetColorNode(nd svg.Node, prop string, prev, pt PaintTypes, sp // SetStroke sets the stroke properties of selected items // based on previous and current PaintType func (cv *Canvas) SetStroke(prev, pt PaintTypes, sp string) { - cv.SetPaintProp("SetStroke", sp, func(nd svg.Node) { + cv.setPaintProp("SetStroke", sp, func(nd svg.Node) { cv.SetColorNode(nd, "stroke", prev, pt, sp) }) } // SetStrokeColor sets the stroke color for selected items. -// manip means currently being manipulated -- don't save undo. -func (cv *Canvas) SetStrokeColor(sp string, manip bool) { - cv.PaintSet(SetStrokeColor, sp, manip, +// which can be done dynamically (for [events.Input] events, final = false, +// followed by a final [events.Change] event (final = true) +func (cv *Canvas) SetStrokeColor(sp string, final bool) { + cv.setPaintPropInput(SetStrokeColor, sp, final, func(itm svg.Node) { p := itm.AsTree().Properties["stroke"] if p != nil { @@ -436,7 +441,7 @@ func (cv *Canvas) SetStrokeColor(sp string, manip bool) { } func (cv *Canvas) SetStrokeWidth(wp string) { - cv.SetPaintProp("SetStrokeWidth", wp, func(nd svg.Node) { + cv.setPaintProp("SetStrokeWidth", wp, func(nd svg.Node) { g := nd.AsNodeBase() if g.Paint.Stroke.Color != nil { g.SetProperty("stroke-width", wp) @@ -447,7 +452,7 @@ func (cv *Canvas) SetStrokeWidth(wp string) { // SetMarkerProperties sets the marker properties func (cv *Canvas) SetMarkerProperties(start, mid, end string, sc, mc, ec MarkerColors) { sv := cv.SVG.SVG - cv.SetPaintProp("SetMarkerProperties", start+" "+mid+" "+end, func(nd svg.Node) { + cv.setPaintProp("SetMarkerProperties", start+" "+mid+" "+end, func(nd svg.Node) { MarkerSetProp(sv, nd, "marker-start", start, sc) MarkerSetProp(sv, nd, "marker-mid", mid, mc) MarkerSetProp(sv, nd, "marker-end", end, ec) @@ -513,15 +518,15 @@ func (cv *Canvas) SetFill(prev, pt PaintTypes, fp string) { cv.ChangeMade() } -// SetFillColor sets the fill color for selected items -// manip means currently being manipulated -- don't save undo. -func (cv *Canvas) SetFillColor(fp string, manip bool) { - cv.PaintSet(SetFillColor, fp, manip, +// SetFillColor sets the fill color for selected items, +// which can be done dynamically (for [events.Input] events, final = false, +// followed by a final [events.Change] event (final = true) +func (cv *Canvas) SetFillColor(fp string, final bool) { + cv.setPaintPropInput(SetFillColor, fp, final, func(itm svg.Node) { p := itm.AsTree().Properties["fill"] if p != nil { itm.AsNodeBase().SetColorProperties("fill", fp) - cv.UpdateMarkerColors(itm) } }) } @@ -548,8 +553,8 @@ func (cv *Canvas) UpdateGradients() { //////// PaintSetter -// Update updates the current settings based on the values in the given Paint and -// properties from node (node can be nil) +// UpdateFromNode updates the current settings based on the values in the given Paint +// Style and properties from node (node can be nil) func (pv *PaintSetter) UpdateFromNode(ps *styles.Paint, nd svg.Node) { pv.StrokeType, pv.StrokeStops = pv.GetPaintType(nd, ps.Stroke.Color, "stroke") pv.FillType, pv.FillStops = pv.GetPaintType(nd, ps.Fill.Color, "fill")