diff --git a/internal/editor/state.go b/internal/editor/state.go index 019b40c..b276ebd 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -374,13 +374,15 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element { // approximation until GlyphLayout-based line tracking is added). line, col := byteOffsetToLineCol(TheState.Editor.Buffer, TheState.Editor.CursorPosition) - // Create the cursor element at the computed position + // Create the cursor element at the computed position. + // Ensure the cursor element knows its bounds for clipping. cursorElem := ui.NewCursor( "editor_cursor", ui.Region{X: cursorX, Y: cursorY, W: ui.Dp(2), H: lineHeight}, line, col, true, ) + cursorElem.ClipRegion = editorRegion return []ui.Element{statusBar, editorElem, cursorElem, bottomBar} } diff --git a/internal/ui/element.go b/internal/ui/element.go index 2a6daeb..13564e1 100644 --- a/internal/ui/element.go +++ b/internal/ui/element.go @@ -480,6 +480,7 @@ func NewSearchBar(region Region, query string, match, total int, forward bool) S type Cursor struct { id string region Region + ClipRegion Region // Region to clip drawing visible bool interactions []Interaction Line int @@ -488,6 +489,7 @@ type Cursor struct { Selection *Selection } +func (c Cursor) NeedsClip() bool { return true } func (c Cursor) Type() string { return "cursor" } func (c Cursor) Region() Region { return c.region } func (c Cursor) Visible() bool { return c.visible } @@ -499,6 +501,17 @@ func (c Cursor) String() string { return fmt.Sprintf("Cursor[%s] region=%+v line=%d col=%d", c.id, c.region, c.Line, c.Column) } func (c Cursor) Draw(gtx layout.Context, r *Renderer) { + // Clip to the cursor's allocated clip region (e.g., the editor text field) + // to prevent drawing over status bars. + var stack *clip.Stack + if c.ClipRegion.W > 0 && c.ClipRegion.H > 0 { + stack = new(clip.Stack) + *stack = clip.Rect{ + Min: image.Point{X: int(r.toPx(c.ClipRegion.X)), Y: int(r.toPx(c.ClipRegion.Y))}, + Max: image.Point{X: int(r.toPx(c.ClipRegion.X + c.ClipRegion.W)), Y: int(r.toPx(c.ClipRegion.Y + c.ClipRegion.H))}, + }.Op().Push(gtx.Ops) + } + // Draw a thin vertical bar (e.g., width 2dp, height 18dp) at the cursor's top-left position // instead of filling the entire region, which covers the text editor. cursorRegion := Region{ @@ -508,6 +521,10 @@ func (c Cursor) Draw(gtx layout.Context, r *Renderer) { H: Dp(18), } r.drawBg(gtx, cursorRegion, Color{R: 0, G: 0, B: 0, A: 255}) + + if stack != nil { + stack.Pop() + } } func NewCursor(id string, region Region, line, col int, blinking bool) Cursor { diff --git a/internal/ui/render.go b/internal/ui/render.go index a762df3..163b00f 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -280,6 +280,15 @@ func (r *Renderer) drawElement(gtx layout.Context, e Element) { } } for _, interaction := range interactive.Interactions() { + if interaction.Gesture == Scroll { + reg, ok := r.scrolls[interactive.ID()] + if !ok { + reg = scrollReg{scroll: &gesture.Scroll{}} + } + reg.scroll.Add(gtx.Ops) + reg.handler = interaction.Handler + r.scrolls[interactive.ID()] = reg + } if interaction.Gesture == Tap { reg, ok := r.clicks[interactive.ID()] if !ok {