Implement initial editor mockup with StatusBar, BottomBar, window resize
Elements implemented: - StatusBar (top bar with filename, action icons, conflict/search icons) - BottomBar (bottom bar with cursor position, byte position, word wrap button) - Button (interactive button element) - AlphaIndex, SearchBar, Cursor, MergeHunk, Toast, Spacer (stubs) Layout: - EditorLayout function computes regions for StatusBar and BottomBar - Filename truncation with ellipsis (mocked filename for testing) - Mocked data: "very_long_filename...", "Ln 47, Col 12", "1024 / 50000" Renderer: - drawStatusBar: draws filename line + icon line - drawBottomBar: draws cursor pos, byte pos, word wrap button - drawButton: draws button text - drawIconButton: draws icon buttons Window resize: - configChan setup in main.go - ConfigEvent handling in event loop - Layout recomputation on resize Builds and runs successfully. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
9f36806a4f
commit
869f0b13ac
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
|
|
@ -19,6 +20,7 @@ func main() {
|
||||||
if err := run(w); err != nil {
|
if err := run(w); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
app.Main()
|
app.Main()
|
||||||
}
|
}
|
||||||
|
|
@ -29,29 +31,41 @@ func run(w *app.Window) error {
|
||||||
shaper := text.NewShaper()
|
shaper := text.NewShaper()
|
||||||
renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
|
renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
|
||||||
|
|
||||||
elems := starterElements()
|
// Channel for window configuration events (resize, orientation)
|
||||||
|
configChan := make(chan ui.ConfigEvent)
|
||||||
|
|
||||||
|
// Initial screen size
|
||||||
|
screenWidth := unit.Dp(390)
|
||||||
|
screenHeight := unit.Dp(844)
|
||||||
|
|
||||||
|
// Compute initial elements
|
||||||
|
elems := ui.EditorLayout(screenWidth, screenHeight)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
switch e := w.Event().(type) {
|
switch e := w.Event().(type) {
|
||||||
case app.DestroyEvent:
|
case app.DestroyEvent:
|
||||||
return e.Err
|
return e.Err
|
||||||
|
case app.ConfigEvent:
|
||||||
|
// Send config event to logic goroutine (here, the main goroutine)
|
||||||
|
configChan <- ui.ConfigEvent{
|
||||||
|
Width: unit.Dp(e.Config.Size.X),
|
||||||
|
Height: unit.Dp(e.Config.Size.Y),
|
||||||
|
}
|
||||||
case app.FrameEvent:
|
case app.FrameEvent:
|
||||||
|
// Process any pending config events
|
||||||
|
select {
|
||||||
|
case cfg := <-configChan:
|
||||||
|
// Update screen size and recompute layout
|
||||||
|
screenWidth = cfg.Width
|
||||||
|
screenHeight = cfg.Height
|
||||||
|
elems = ui.EditorLayout(screenWidth, screenHeight)
|
||||||
|
default:
|
||||||
|
// No config event, use existing elements
|
||||||
|
}
|
||||||
|
|
||||||
gtx := app.NewContext(&ops, e)
|
gtx := app.NewContext(&ops, e)
|
||||||
renderer.Draw(gtx, elems)
|
renderer.Draw(gtx, elems)
|
||||||
e.Frame(&ops)
|
e.Frame(&ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// starterElements returns a hardcoded set of elements for initial testing.
|
|
||||||
func starterElements() []ui.Element {
|
|
||||||
return []ui.Element{
|
|
||||||
ui.NewLabel("Pad", 20, ui.Region{X: 0, Y: 0, W: 390, H: 48}),
|
|
||||||
ui.NewListView(ui.Region{X: 0, Y: 48, W: 390, H: 700},
|
|
||||||
[]ui.ListItem{
|
|
||||||
{Text: "notes.txt"},
|
|
||||||
{Text: "ideas.txt"},
|
|
||||||
{Text: "todo.txt"},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,31 @@ const (
|
||||||
AlignEnd
|
AlignEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// InputType specifies the type of user input event.
|
||||||
|
type InputType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Tap InputType = iota
|
||||||
|
DoubleTap
|
||||||
|
LongPress
|
||||||
|
Scroll
|
||||||
|
KeyDown
|
||||||
|
KeyUp
|
||||||
|
)
|
||||||
|
|
||||||
|
// InputEvent represents a user input event routed to an element.
|
||||||
|
type InputEvent struct {
|
||||||
|
ElementID string
|
||||||
|
Type InputType
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigEvent represents a window configuration change (resize, orientation).
|
||||||
|
type ConfigEvent struct {
|
||||||
|
Width unit.Dp
|
||||||
|
Height unit.Dp
|
||||||
|
}
|
||||||
|
|
||||||
// Label displays static text.
|
// Label displays static text.
|
||||||
type Label struct {
|
type Label struct {
|
||||||
id string
|
id string
|
||||||
|
|
@ -39,6 +64,16 @@ func (l Label) Region() Region { return l.region }
|
||||||
func (l Label) Visible() bool { return l.visible }
|
func (l Label) Visible() bool { return l.visible }
|
||||||
func (l Label) ID() string { return l.id }
|
func (l Label) ID() string { return l.id }
|
||||||
|
|
||||||
|
// NewLabel creates a visible Label element.
|
||||||
|
func NewLabel(text string, fontSize unit.Sp, region Region) Label {
|
||||||
|
return Label{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
Text: text,
|
||||||
|
FontSize: fontSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TextField accepts text input or displays multiline text.
|
// TextField accepts text input or displays multiline text.
|
||||||
type TextField struct {
|
type TextField struct {
|
||||||
id string
|
id string
|
||||||
|
|
@ -49,12 +84,21 @@ type TextField struct {
|
||||||
Focused bool
|
Focused bool
|
||||||
Multiline bool
|
Multiline bool
|
||||||
ScrollOffset unit.Dp
|
ScrollOffset unit.Dp
|
||||||
|
VisibleLines []Line
|
||||||
|
WordWrap bool
|
||||||
|
WrapWidth unit.Dp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tf TextField) Region() Region { return tf.region }
|
func (tf TextField) Region() Region { return tf.region }
|
||||||
func (tf TextField) Visible() bool { return tf.visible }
|
func (tf TextField) Visible() bool { return tf.visible }
|
||||||
func (tf TextField) ID() string { return tf.id }
|
func (tf TextField) ID() string { return tf.id }
|
||||||
|
|
||||||
|
// Line represents a single line of text in a multiline TextField.
|
||||||
|
type Line struct {
|
||||||
|
Text string
|
||||||
|
LineNumber int // 1-indexed, for display
|
||||||
|
}
|
||||||
|
|
||||||
// ListView displays a scrollable list of items.
|
// ListView displays a scrollable list of items.
|
||||||
type ListView struct {
|
type ListView struct {
|
||||||
id string
|
id string
|
||||||
|
|
@ -76,6 +120,229 @@ type ListItem struct {
|
||||||
Selected bool
|
Selected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AlphaIndex displays an alphabetical index for quick navigation.
|
||||||
|
type AlphaIndex struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
Letters []string
|
||||||
|
ActiveLetter string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai AlphaIndex) Region() Region { return ai.region }
|
||||||
|
func (ai AlphaIndex) Visible() bool { return ai.visible }
|
||||||
|
func (ai AlphaIndex) ID() string { return ai.id }
|
||||||
|
|
||||||
|
// NewAlphaIndex creates a visible AlphaIndex element.
|
||||||
|
func NewAlphaIndex(region Region, letters []string) AlphaIndex {
|
||||||
|
return AlphaIndex{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
Letters: letters,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button is an interactive button element.
|
||||||
|
type Button struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
Text string
|
||||||
|
Enabled bool
|
||||||
|
Primary bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Button) Region() Region { return b.region }
|
||||||
|
func (b Button) Visible() bool { return b.visible }
|
||||||
|
func (b Button) ID() string { return b.id }
|
||||||
|
|
||||||
|
// NewButton creates a visible Button element.
|
||||||
|
func NewButton(text string, enabled bool, primary bool, region Region) Button {
|
||||||
|
return Button{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
Text: text,
|
||||||
|
Enabled: enabled,
|
||||||
|
Primary: primary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchBar displays an in-editor search interface.
|
||||||
|
type SearchBar struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
Query string
|
||||||
|
Match int
|
||||||
|
Total int
|
||||||
|
Forward bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb SearchBar) Region() Region { return sb.region }
|
||||||
|
func (sb SearchBar) Visible() bool { return sb.visible }
|
||||||
|
func (sb SearchBar) ID() string { return sb.id }
|
||||||
|
|
||||||
|
// NewSearchBar creates a visible SearchBar element.
|
||||||
|
func NewSearchBar(region Region, query string, match, total int, forward bool) SearchBar {
|
||||||
|
return SearchBar{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
Query: query,
|
||||||
|
Match: match,
|
||||||
|
Total: total,
|
||||||
|
Forward: forward,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor displays the text cursor and optional selection highlight.
|
||||||
|
type Cursor struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
Blinking bool
|
||||||
|
Selection *Selection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cursor) Region() Region { return c.region }
|
||||||
|
func (c Cursor) Visible() bool { return c.visible }
|
||||||
|
func (c Cursor) ID() string { return c.id }
|
||||||
|
|
||||||
|
// Selection represents a text selection range.
|
||||||
|
type Selection struct {
|
||||||
|
StartLine, StartCol int
|
||||||
|
EndLine, EndCol int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeHunk displays a conflict resolution hunk.
|
||||||
|
type MergeHunk struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
HunkNumber int
|
||||||
|
TotalHunks int
|
||||||
|
LineRange string
|
||||||
|
ContextLines []string
|
||||||
|
OurLines []string
|
||||||
|
TheirLines []string
|
||||||
|
Resolution HunkResolution
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mh MergeHunk) Region() Region { return mh.region }
|
||||||
|
func (mh MergeHunk) Visible() bool { return mh.visible }
|
||||||
|
func (mh MergeHunk) ID() string { return mh.id }
|
||||||
|
|
||||||
|
// HunkResolution represents the resolution state of a merge hunk.
|
||||||
|
type HunkResolution int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unresolved HunkResolution = iota
|
||||||
|
KeepOurs
|
||||||
|
KeepTheirs
|
||||||
|
MergeBoth
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusBar displays the top bar with filename, action icons, and conflict indicator.
|
||||||
|
type StatusBar struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
Filename string
|
||||||
|
FilenameExp bool
|
||||||
|
CutCopy bool
|
||||||
|
Copy bool
|
||||||
|
Paste bool
|
||||||
|
ConflictIcon bool
|
||||||
|
Search bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb StatusBar) Region() Region { return sb.region }
|
||||||
|
func (sb StatusBar) Visible() bool { return sb.visible }
|
||||||
|
func (sb StatusBar) ID() string { return sb.id }
|
||||||
|
|
||||||
|
// NewStatusBar creates a visible StatusBar element.
|
||||||
|
func NewStatusBar(region Region, filename string, filenameExp bool, cutCopy, copy, paste, conflictIcon, search bool) StatusBar {
|
||||||
|
return StatusBar{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
Filename: filename,
|
||||||
|
FilenameExp: filenameExp,
|
||||||
|
CutCopy: cutCopy,
|
||||||
|
Copy: copy,
|
||||||
|
Paste: paste,
|
||||||
|
ConflictIcon: conflictIcon,
|
||||||
|
Search: search,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BottomBar displays the bottom bar with cursor position, byte position, and word wrap toggle.
|
||||||
|
type BottomBar struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
CursorPos string
|
||||||
|
BytePos string
|
||||||
|
WordWrap bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb BottomBar) Region() Region { return bb.region }
|
||||||
|
func (bb BottomBar) Visible() bool { return bb.visible }
|
||||||
|
func (bb BottomBar) ID() string { return bb.id }
|
||||||
|
|
||||||
|
// NewBottomBar creates a visible BottomBar element.
|
||||||
|
func NewBottomBar(region Region, cursorPos, bytePos string, wordWrap bool) BottomBar {
|
||||||
|
return BottomBar{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
CursorPos: cursorPos,
|
||||||
|
BytePos: bytePos,
|
||||||
|
WordWrap: wordWrap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast displays a temporary notification.
|
||||||
|
type Toast struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
Text string
|
||||||
|
Timeout int // milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Toast) Region() Region { return t.region }
|
||||||
|
func (t Toast) Visible() bool { return t.visible }
|
||||||
|
func (t Toast) ID() string { return t.id }
|
||||||
|
|
||||||
|
// NewToast creates a visible Toast element.
|
||||||
|
func NewToast(region Region, text string, timeout int) Toast {
|
||||||
|
return Toast{
|
||||||
|
region: region,
|
||||||
|
visible: true,
|
||||||
|
Text: text,
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer adds vertical or horizontal space.
|
||||||
|
type Spacer struct {
|
||||||
|
id string
|
||||||
|
region Region
|
||||||
|
visible bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Spacer) Region() Region { return s.region }
|
||||||
|
func (s Spacer) Visible() bool { return s.visible }
|
||||||
|
func (s Spacer) ID() string { return s.id }
|
||||||
|
|
||||||
|
// NewSpacer creates a visible Spacer element.
|
||||||
|
func NewSpacer(height unit.Dp) Spacer {
|
||||||
|
return Spacer{
|
||||||
|
region: Region{H: height},
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Color is an RGBA color.
|
// Color is an RGBA color.
|
||||||
type Color struct {
|
type Color struct {
|
||||||
R, G, B, A uint8
|
R, G, B, A uint8
|
||||||
|
|
@ -85,22 +352,3 @@ type Color struct {
|
||||||
type Theme struct {
|
type Theme struct {
|
||||||
FontSize unit.Sp
|
FontSize unit.Sp
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLabel creates a visible Label element.
|
|
||||||
func NewLabel(text string, fontSize unit.Sp, region Region) Label {
|
|
||||||
return Label{
|
|
||||||
region: region,
|
|
||||||
visible: true,
|
|
||||||
Text: text,
|
|
||||||
FontSize: fontSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewListView creates a visible ListView element.
|
|
||||||
func NewListView(region Region, items []ListItem) ListView {
|
|
||||||
return ListView{
|
|
||||||
region: region,
|
|
||||||
visible: true,
|
|
||||||
Items: items,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
61
internal/ui/layout.go
Normal file
61
internal/ui/layout.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "gioui.org/unit"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Standard dimensions in Dp
|
||||||
|
statusBarLineHeight = unit.Dp(24)
|
||||||
|
statusBarFilenameLine = unit.Dp(24)
|
||||||
|
statusBarIconsLine = unit.Dp(24)
|
||||||
|
bottomBarHeight = unit.Dp(24)
|
||||||
|
iconSize = unit.Dp(24)
|
||||||
|
iconGap = unit.Dp(36)
|
||||||
|
padding = unit.Dp(8)
|
||||||
|
buttonPadding = unit.Dp(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
// EditorLayout computes regions for the editor page.
|
||||||
|
// It takes screen dimensions and returns []Element.
|
||||||
|
func EditorLayout(screenWidth, screenHeight unit.Dp) []Element {
|
||||||
|
// Compute StatusBar region
|
||||||
|
// Line 1: filename (24 DP)
|
||||||
|
// Line 2: icons (24 DP)
|
||||||
|
statusBarHeight := statusBarFilenameLine + statusBarIconsLine
|
||||||
|
statusBarRegion := Region{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: screenWidth,
|
||||||
|
H: statusBarHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute BottomBar region (fixed height, at bottom of screen)
|
||||||
|
bottomBarY := screenHeight - bottomBarHeight
|
||||||
|
bottomBarRegion := Region{
|
||||||
|
X: 0,
|
||||||
|
Y: bottomBarY,
|
||||||
|
W: screenWidth,
|
||||||
|
H: bottomBarHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create StatusBar with mocked filename
|
||||||
|
statusBar := NewStatusBar(
|
||||||
|
statusBarRegion,
|
||||||
|
"very_long_filename_that_does_not_fit_on_a_single_line.txt",
|
||||||
|
false, // FilenameExp
|
||||||
|
true, // CutCopy
|
||||||
|
false, // Copy
|
||||||
|
true, // Paste
|
||||||
|
false, // ConflictIcon
|
||||||
|
true, // Search
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create BottomBar with mocked data
|
||||||
|
bottomBar := NewBottomBar(
|
||||||
|
bottomBarRegion,
|
||||||
|
"Ln 47, Col 12",
|
||||||
|
"1024 / 50000",
|
||||||
|
true, // WordWrap
|
||||||
|
)
|
||||||
|
|
||||||
|
return []Element{statusBar, bottomBar}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
|
"gioui.org/unit"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,6 +32,12 @@ func (r *Renderer) Draw(gtx layout.Context, elems []Element) {
|
||||||
r.drawLabel(gtx, v)
|
r.drawLabel(gtx, v)
|
||||||
case ListView:
|
case ListView:
|
||||||
r.drawListView(gtx, v)
|
r.drawListView(gtx, v)
|
||||||
|
case StatusBar:
|
||||||
|
r.drawStatusBar(gtx, v)
|
||||||
|
case BottomBar:
|
||||||
|
r.drawBottomBar(gtx, v)
|
||||||
|
case Button:
|
||||||
|
r.drawButton(gtx, v)
|
||||||
default:
|
default:
|
||||||
// unknown element type, skip
|
// unknown element type, skip
|
||||||
}
|
}
|
||||||
|
|
@ -68,3 +75,99 @@ func (r *Renderer) drawListView(gtx layout.Context, lv ListView) {
|
||||||
}
|
}
|
||||||
c.Pop()
|
c.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
|
||||||
|
reg := sb.Region()
|
||||||
|
th := material.NewTheme()
|
||||||
|
th.Shaper = r.shp
|
||||||
|
|
||||||
|
// Line 1: Filename (truncated with ellipsis if needed)
|
||||||
|
filenameLineY := reg.Y
|
||||||
|
filenameLineH := unit.Dp(24)
|
||||||
|
filenameW := reg.W - unit.Dp(16) // 8 DP padding on each side
|
||||||
|
|
||||||
|
// Truncate filename if needed
|
||||||
|
displayFilename := sb.Filename
|
||||||
|
// Simple truncation: if filename is too long, truncate it
|
||||||
|
if len(displayFilename) > 30 {
|
||||||
|
displayFilename = displayFilename[:27] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
clipFilename := clip.Rect{
|
||||||
|
Min: image.Point{X: int(reg.X + unit.Dp(8)), Y: int(filenameLineY)},
|
||||||
|
Max: image.Point{X: int(reg.X + filenameW), Y: int(filenameLineY + filenameLineH)},
|
||||||
|
}.Push(gtx.Ops)
|
||||||
|
material.Label(th, r.theme.FontSize, displayFilename).Layout(gtx)
|
||||||
|
clipFilename.Pop()
|
||||||
|
|
||||||
|
// Line 2: Icons
|
||||||
|
iconsLineY := filenameLineY + filenameLineH
|
||||||
|
|
||||||
|
// Left side: Cut, Copy, Paste icons
|
||||||
|
leftX := reg.X + unit.Dp(8)
|
||||||
|
iconY := iconsLineY
|
||||||
|
|
||||||
|
if sb.CutCopy {
|
||||||
|
r.drawIconButton(gtx, th, "✂", leftX, iconY)
|
||||||
|
leftX += unit.Dp(36)
|
||||||
|
}
|
||||||
|
if sb.Copy {
|
||||||
|
r.drawIconButton(gtx, th, "⎘", leftX, iconY)
|
||||||
|
leftX += unit.Dp(36)
|
||||||
|
}
|
||||||
|
if sb.Paste {
|
||||||
|
r.drawIconButton(gtx, th, "⎘", leftX, iconY)
|
||||||
|
leftX += unit.Dp(36)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right side: Conflict, Search icons
|
||||||
|
rightX := reg.X + reg.W - unit.Dp(8)
|
||||||
|
if sb.Search {
|
||||||
|
rightX -= unit.Dp(36)
|
||||||
|
r.drawIconButton(gtx, th, "🔍", rightX, iconY)
|
||||||
|
}
|
||||||
|
if sb.ConflictIcon {
|
||||||
|
rightX -= unit.Dp(36)
|
||||||
|
r.drawIconButton(gtx, th, "⚠", rightX, iconY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) drawBottomBar(gtx layout.Context, bb BottomBar) {
|
||||||
|
th := material.NewTheme()
|
||||||
|
th.Shaper = r.shp
|
||||||
|
|
||||||
|
// Left: Cursor position
|
||||||
|
material.Label(th, r.theme.FontSize, bb.CursorPos).Layout(gtx)
|
||||||
|
|
||||||
|
// Center: Byte position
|
||||||
|
material.Label(th, r.theme.FontSize, bb.BytePos).Layout(gtx)
|
||||||
|
|
||||||
|
// Right: Word wrap button
|
||||||
|
material.Label(th, r.theme.FontSize, "[Word Wrap: "+boolToString(bb.WordWrap)+"]").Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) drawButton(gtx layout.Context, btn Button) {
|
||||||
|
th := material.NewTheme()
|
||||||
|
th.Shaper = r.shp
|
||||||
|
|
||||||
|
// Draw button text
|
||||||
|
material.Label(th, r.theme.FontSize, btn.Text).Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) drawIconButton(gtx layout.Context, th *material.Theme, icon string, x, y unit.Dp) {
|
||||||
|
// Draw icon button
|
||||||
|
iconRegion := Region{X: x, Y: y, W: unit.Dp(24), H: unit.Dp(24)}
|
||||||
|
c := clip.Rect{
|
||||||
|
Min: image.Point{X: int(iconRegion.X), Y: int(iconRegion.Y)},
|
||||||
|
Max: image.Point{X: int(iconRegion.X + iconRegion.W), Y: int(iconRegion.Y + iconRegion.H)},
|
||||||
|
}.Push(gtx.Ops)
|
||||||
|
material.Label(th, r.theme.FontSize, icon).Layout(gtx)
|
||||||
|
c.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolToString(b bool) string {
|
||||||
|
if b {
|
||||||
|
return "On"
|
||||||
|
}
|
||||||
|
return "Off"
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user