Revert to last working state (68437c3) before PNG icon changes
This commit is contained in:
parent
1b1cf51528
commit
fe3e7c3d75
|
|
@ -38,15 +38,12 @@ func run(w *app.Window) error {
|
||||||
initialDPW := ui.Dp(390)
|
initialDPW := ui.Dp(390)
|
||||||
initialDPH := ui.Dp(844)
|
initialDPH := ui.Dp(844)
|
||||||
|
|
||||||
// Track previous scale to detect changes
|
// Track pixel dimensions from ConfigEvent (for resize handling)
|
||||||
prevScale := float32(1.0)
|
var pixelW, pixelH int
|
||||||
|
|
||||||
// Create logic instance with initial DP size and scale=1.0
|
// Create logic instance with initial DP size and scale=1.0
|
||||||
logic := editor.NewLogic(initialDPW, initialDPH)
|
logic := editor.NewLogic(initialDPW, initialDPH)
|
||||||
|
|
||||||
// Set State reference on Renderer — it reads scale/window size from State directly
|
|
||||||
renderer.SetState(logic.State())
|
|
||||||
|
|
||||||
// Shared state protected by mutex
|
// Shared state protected by mutex
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
var elems []ui.Element
|
var elems []ui.Element
|
||||||
|
|
@ -63,9 +60,12 @@ func run(w *app.Window) error {
|
||||||
case app.DestroyEvent:
|
case app.DestroyEvent:
|
||||||
return e.Err
|
return e.Err
|
||||||
case app.ConfigEvent:
|
case app.ConfigEvent:
|
||||||
// Convert pixel dimensions to DP using current scale (starts at 1.0)
|
// Store pixel dimensions from ConfigEvent
|
||||||
dpW := ui.ToDp(ui.Px(e.Config.Size.X), 1.0)
|
pixelW = e.Config.Size.X
|
||||||
dpH := ui.ToDp(ui.Px(e.Config.Size.Y), 1.0)
|
pixelH = e.Config.Size.Y
|
||||||
|
// Convert to DP using current scale (starts at 1.0)
|
||||||
|
dpW := ui.ToDp(ui.Px(pixelW), 1.0)
|
||||||
|
dpH := ui.ToDp(ui.Px(pixelH), 1.0)
|
||||||
// Send config event to logic goroutine (in DP)
|
// Send config event to logic goroutine (in DP)
|
||||||
logic.ConfigChan() <- editor.ConfigEvent{
|
logic.ConfigChan() <- editor.ConfigEvent{
|
||||||
Width: dpW,
|
Width: dpW,
|
||||||
|
|
@ -73,20 +73,30 @@ func run(w *app.Window) error {
|
||||||
}
|
}
|
||||||
case app.FrameEvent:
|
case app.FrameEvent:
|
||||||
gtx := app.NewContext(&ops, e)
|
gtx := app.NewContext(&ops, e)
|
||||||
newScale := gtx.Metric.PxPerDp
|
|
||||||
|
|
||||||
// If scale changed, send scale event to logic goroutine
|
// Get scale from State — this is updated by ScaleEvent
|
||||||
if newScale != prevScale {
|
newScale := logic.Scale()
|
||||||
logic.ConfigChan() <- editor.ScaleEvent{Scale: newScale}
|
|
||||||
prevScale = newScale
|
// Convert pixel dimensions to DP using actual scale
|
||||||
|
dpW := ui.ToDp(ui.Px(pixelW), newScale)
|
||||||
|
dpH := ui.ToDp(ui.Px(pixelH), newScale)
|
||||||
|
|
||||||
|
// Send updated config to logic goroutine
|
||||||
|
logic.ConfigChan() <- editor.ConfigEvent{
|
||||||
|
Width: dpW,
|
||||||
|
Height: dpH,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acquire mutex, read frame, draw, e.Frame(), release mutex
|
// Update renderer with current scale from State
|
||||||
|
renderer.SetScale(newScale)
|
||||||
|
|
||||||
|
// Acquire mutex, read frame, draw, release mutex
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
currentElems := elems
|
currentElems := elems
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
renderer.Draw(gtx, currentElems)
|
renderer.Draw(gtx, currentElems)
|
||||||
e.Frame(&ops)
|
e.Frame(&ops)
|
||||||
mu.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ type ConfigUpdate interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ConfigEvent) apply(s *State) {
|
func (e ConfigEvent) apply(s *State) {
|
||||||
s.SetSize(e.Width, e.Height)
|
s.ScreenWidth = e.Width
|
||||||
|
s.ScreenHeight = e.Height
|
||||||
|
s.Elems = EditorLayout(e.Width, e.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ScaleEvent) apply(s *State) {
|
func (e ScaleEvent) apply(s *State) {
|
||||||
|
|
@ -91,14 +93,14 @@ func (l *Logic) ResultChan() <-chan ResultEvent {
|
||||||
func (l *Logic) ScreenSize() (ui.Dp, ui.Dp) {
|
func (l *Logic) ScreenSize() (ui.Dp, ui.Dp) {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
return l.state.ScreenWidth(), l.state.ScreenHeight()
|
return l.state.ScreenWidth, l.state.ScreenHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale returns the current scale factor (pixels per DP).
|
// Scale returns the current scale factor (pixels per DP).
|
||||||
func (l *Logic) Scale() float32 {
|
func (l *Logic) Scale() float32 {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
return l.state.Scale()
|
return l.state.Scale
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs the logic goroutine loop.
|
// Run runs the logic goroutine loop.
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
// State holds all application state owned by the logic goroutine.
|
// State holds all application state owned by the logic goroutine.
|
||||||
// All dimensions are in device-independent pixels (Dp).
|
// All dimensions are in device-independent pixels (Dp).
|
||||||
type State struct {
|
type State struct {
|
||||||
screenWidth ui.Dp
|
ScreenWidth ui.Dp
|
||||||
screenHeight ui.Dp
|
ScreenHeight ui.Dp
|
||||||
scale float32 // pixels per DP, default 1.0
|
Scale float32 // pixels per DP, default 1.0
|
||||||
Elems []ui.Element
|
Elems []ui.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -18,33 +18,17 @@ type State struct {
|
||||||
// scale defaults to 1.0 until updated by ScaleEvent.
|
// scale defaults to 1.0 until updated by ScaleEvent.
|
||||||
func NewState(screenWidth, screenHeight ui.Dp) *State {
|
func NewState(screenWidth, screenHeight ui.Dp) *State {
|
||||||
return &State{
|
return &State{
|
||||||
screenWidth: screenWidth,
|
ScreenWidth: screenWidth,
|
||||||
screenHeight: screenHeight,
|
ScreenHeight: screenHeight,
|
||||||
scale: 1.0,
|
Scale: 1.0,
|
||||||
Elems: EditorLayout(screenWidth, screenHeight),
|
Elems: EditorLayout(screenWidth, screenHeight),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale returns the current scale factor (pixels per DP).
|
|
||||||
func (s *State) Scale() float32 { return s.scale }
|
|
||||||
|
|
||||||
// ScreenWidth returns the current screen width in Dp.
|
|
||||||
func (s *State) ScreenWidth() ui.Dp { return s.screenWidth }
|
|
||||||
|
|
||||||
// ScreenHeight returns the current screen height in Dp.
|
|
||||||
func (s *State) ScreenHeight() ui.Dp { return s.screenHeight }
|
|
||||||
|
|
||||||
// SetScale updates the scale factor and recomputes layout.
|
// SetScale updates the scale factor and recomputes layout.
|
||||||
func (s *State) SetScale(scale float32) {
|
func (s *State) SetScale(scale float32) {
|
||||||
s.scale = scale
|
s.Scale = scale
|
||||||
s.Elems = EditorLayout(s.screenWidth, s.screenHeight)
|
s.Elems = EditorLayout(s.ScreenWidth, s.ScreenHeight)
|
||||||
}
|
|
||||||
|
|
||||||
// SetSize updates the screen dimensions and recomputes layout.
|
|
||||||
func (s *State) SetSize(width, height ui.Dp) {
|
|
||||||
s.screenWidth = width
|
|
||||||
s.screenHeight = height
|
|
||||||
s.Elems = EditorLayout(width, height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditorLayout computes regions for the editor page.
|
// EditorLayout computes regions for the editor page.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"embed"
|
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
_ "image/png"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
|
|
@ -19,75 +15,43 @@ import (
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed icons/*.png
|
|
||||||
var iconFS embed.FS
|
|
||||||
|
|
||||||
// StateReader provides access to scale and window dimensions from State.
|
|
||||||
// Implemented by editor.State to avoid import cycles.
|
|
||||||
type StateReader interface {
|
|
||||||
Scale() float32
|
|
||||||
ScreenWidth() Dp
|
|
||||||
ScreenHeight() Dp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renderer consumes a slice of elements and draws them.
|
// Renderer consumes a slice of elements and draws them.
|
||||||
type Renderer struct {
|
type Renderer struct {
|
||||||
theme Theme
|
theme Theme
|
||||||
shp *text.Shaper
|
shp *text.Shaper
|
||||||
state StateReader // read-only access to State for scale/window size
|
scale float32 // pixels per DP, from State.Scale
|
||||||
icons map[string]image.Image // loaded PNG icons
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Renderer.
|
// New creates a new Renderer.
|
||||||
func New(th Theme, shp *text.Shaper) *Renderer {
|
func New(th Theme, shp *text.Shaper) *Renderer {
|
||||||
r := &Renderer{theme: th, shp: shp, icons: make(map[string]image.Image)}
|
return &Renderer{theme: th, shp: shp, scale: 1.0}
|
||||||
r.loadIcons()
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadIcons loads all PNG icons from the embedded filesystem.
|
// SetScale updates the renderer's scale factor from State.Scale.
|
||||||
func (r *Renderer) loadIcons() {
|
func (r *Renderer) SetScale(scale float32) {
|
||||||
files := []string{"cut.png", "copy.png", "paste.png", "search.png", "conflict.png", "wrapOn.png", "wrapOff.png"}
|
r.scale = scale
|
||||||
for _, filename := range files {
|
|
||||||
data, err := iconFS.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
continue // skip missing icons
|
|
||||||
}
|
|
||||||
img, _, err := image.Decode(bytes.NewReader(data))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := strings.TrimSuffix(filename, ".png")
|
|
||||||
r.icons[name] = img
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetState sets the State reference for the Renderer.
|
// toPx converts Dp to physical pixels using the renderer's scale.
|
||||||
// The Renderer reads scale and window size from State directly.
|
|
||||||
func (r *Renderer) SetState(state StateReader) {
|
|
||||||
r.state = state
|
|
||||||
}
|
|
||||||
|
|
||||||
// toPx converts Dp to physical pixels using the scale from State.
|
|
||||||
func (r *Renderer) toPx(dp Dp) Px {
|
func (r *Renderer) toPx(dp Dp) Px {
|
||||||
return ToPx(dp, r.state.Scale())
|
return ToPx(dp, r.scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// toDp converts physical pixels to Dp using the scale from State.
|
// toDp converts physical pixels to Dp using the renderer's scale.
|
||||||
func (r *Renderer) toDp(px Px) Dp {
|
func (r *Renderer) toDp(px Px) Dp {
|
||||||
return ToDp(px, r.state.Scale())
|
return ToDp(px, r.scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw iterates elements and draws each in slice order (back-to-front).
|
// Draw iterates elements and draws each in slice order (back-to-front).
|
||||||
// All scale and window size values come from State, never from gtx.
|
// It captures the initial constraints once at the start, then passes them
|
||||||
|
// to each render function for consistent positioning.
|
||||||
|
// The scale is set externally via SetScale() from State.Scale.
|
||||||
func (r *Renderer) Draw(gtx layout.Context, elems []Element) {
|
func (r *Renderer) Draw(gtx layout.Context, elems []Element) {
|
||||||
// Use window dimensions from State — convert DP to pixels using State.Scale
|
// Capture initial constraints once — before any clips modify them.
|
||||||
state := r.state
|
// These are in physical pixels and serve as the window bounds for all positioning.
|
||||||
windowWPx := int(r.toPx(state.ScreenWidth()))
|
|
||||||
windowHPx := int(r.toPx(state.ScreenHeight()))
|
|
||||||
initialConstraints := WindowConstraints{
|
initialConstraints := WindowConstraints{
|
||||||
Min: image.Point{X: 0, Y: 0},
|
Min: gtx.Constraints.Min,
|
||||||
Max: image.Point{X: windowWPx, Y: windowHPx},
|
Max: gtx.Constraints.Max,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range elems {
|
for _, e := range elems {
|
||||||
|
|
@ -194,15 +158,22 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar, _ WindowConst
|
||||||
// Left side: Cut, Copy, Paste icons (always visible)
|
// Left side: Cut, Copy, Paste icons (always visible)
|
||||||
leftX := reg.X + Dp(8)
|
leftX := reg.X + Dp(8)
|
||||||
iconY := iconsLineY
|
iconY := iconsLineY
|
||||||
iconSize := Dp(16) // 16dp icons
|
|
||||||
|
|
||||||
r.drawPng(gtx, r.icons["cut"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
r.drawText(gtx, th.Shaper, "✂", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
||||||
leftX += Dp(28)
|
leftX += Dp(36)
|
||||||
r.drawPng(gtx, r.icons["copy"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
||||||
leftX += Dp(28)
|
leftX += Dp(36)
|
||||||
r.drawPng(gtx, r.icons["paste"], leftX, iconY, iconSize, iconSize, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
||||||
leftX += Dp(12)
|
leftX += Dp(12)
|
||||||
|
|
||||||
|
// Right side: Conflict (conditional), Search (always visible)
|
||||||
|
rightX := reg.X + reg.W - Dp(8)
|
||||||
|
if sb.ConflictIcon {
|
||||||
|
rightX -= Dp(36)
|
||||||
|
r.drawText(gtx, th.Shaper, "⚠", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
||||||
|
}
|
||||||
|
rightX -= Dp(36)
|
||||||
|
r.drawText(gtx, th.Shaper, "🔍", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
|
||||||
|
|
||||||
c.Pop()
|
c.Pop()
|
||||||
}
|
}
|
||||||
|
|
@ -392,42 +363,6 @@ func (r *Renderer) drawLine(gtx layout.Context, shp *text.Shaper, line []text.Gl
|
||||||
t.Pop()
|
t.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawPng draws a PNG image at the given position and size.
|
|
||||||
// x, y are in Dp (top-left corner).
|
|
||||||
// width, height are in Dp (target size).
|
|
||||||
// col is the tint color — if alpha < 255, the image is tinted using blendOp.DstIn.
|
|
||||||
func (r *Renderer) drawPng(gtx layout.Context, img image.Image, x, y Dp, width, height Dp, col color.NRGBA) {
|
|
||||||
if img == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Dp to pixels
|
|
||||||
xPx := int(r.toPx(x))
|
|
||||||
yPx := int(r.toPx(y))
|
|
||||||
wPx := int(r.toPx(width))
|
|
||||||
hPx := int(r.toPx(height))
|
|
||||||
|
|
||||||
// Compute source rectangle (full image)
|
|
||||||
src := image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy())
|
|
||||||
_ = src // unused, but kept for clarity
|
|
||||||
|
|
||||||
// Compute destination rectangle
|
|
||||||
dst := image.Rect(xPx, yPx, xPx+wPx, yPx+hPx)
|
|
||||||
|
|
||||||
// If tint color has alpha < 255, apply tint using blendOp.DstIn
|
|
||||||
if col.A < 255 {
|
|
||||||
// Draw tinted rectangle over the image
|
|
||||||
bgClip := clip.Rect(dst).Push(gtx.Ops)
|
|
||||||
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
|
||||||
paint.PaintOp{}.Add(gtx.Ops)
|
|
||||||
bgClip.Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the image
|
|
||||||
paint.NewImageOp(img).Add(gtx.Ops)
|
|
||||||
paint.PaintOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowConstraints tracks the initial window constraints captured at the start of Draw.
|
// WindowConstraints tracks the initial window constraints captured at the start of Draw.
|
||||||
// All elements are positioned relative to these bounds to ensure consistency.
|
// All elements are positioned relative to these bounds to ensure consistency.
|
||||||
type WindowConstraints struct {
|
type WindowConstraints struct {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user