diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
new file mode 100644
index 000000000..767218d85
--- /dev/null
+++ b/.idea/dictionaries/project.xml
@@ -0,0 +1,8 @@
+
+
+
+ fbcon
+ manjeet
+
+
+
\ No newline at end of file
diff --git a/cmd/compositor/desktop.go b/cmd/compositor/desktop.go
new file mode 100644
index 000000000..a3b11082f
--- /dev/null
+++ b/cmd/compositor/desktop.go
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2025 Manjeet Singh .
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "image"
+ "image/color"
+
+ "rlxos.dev/pkg/graphics/canvas"
+ "rlxos.dev/pkg/graphics/event"
+ "rlxos.dev/pkg/graphics/widget"
+)
+
+type Desktop struct {
+ widget.BaseWidget
+
+ Background color.Color
+ Panel Panel
+ windows []widget.Widget
+}
+
+func NewDesktop() *Desktop {
+ d := &Desktop{
+ Background: color.Black,
+ windows: []widget.Widget{},
+ }
+
+ return d
+}
+
+func (d *Desktop) AddWindow(w widget.Widget) {
+ d.windows = append(d.windows, w)
+}
+
+func (d *Desktop) SetBounds(rect image.Rectangle) {
+ d.BaseWidget.SetBounds(rect)
+ d.Panel.SetBounds(rect)
+}
+
+func (d *Desktop) Draw(c canvas.Canvas) {
+ c.SetColor(d.Background)
+ c.FillRect(d.Bounds)
+
+ for _, w := range d.windows {
+ w.Draw(c)
+ }
+
+ d.Panel.Draw(c)
+}
+
+func (d *Desktop) Update(e event.Event) {
+ d.Panel.Update(e)
+}
diff --git a/cmd/compositor/main.go b/cmd/compositor/main.go
index 9f5e1a409..af82de334 100644
--- a/cmd/compositor/main.go
+++ b/cmd/compositor/main.go
@@ -1,37 +1,14 @@
package main
import (
- "image/color"
"log"
"rlxos.dev/pkg/graphics/app"
- "rlxos.dev/pkg/graphics/driver/fbdev"
- "rlxos.dev/pkg/graphics/widget"
)
func main() {
- if err := app.Run(&widget.Container{
- Orientation: widget.Horizontal,
- Spacing: 10,
- Children: []widget.Widget{
- &widget.Button{
- BackgroundColor: color.RGBA{R: 33, G: 33, B: 33, A: 255},
- HoverColor: color.RGBA{97, 97, 97, 255},
- OnClick: func() {
- log.Println("Clicked 1")
- },
- },
- &widget.Button{
- BackgroundColor: color.RGBA{R: 88, G: 88, B: 88, A: 255},
- HoverColor: color.RGBA{97, 97, 97, 255},
- OnClick: func() {
- log.Println("Clicked 2")
- },
- },
- },
- }, &fbdev.Driver{
- Device: "/dev/fb0",
- }); err != nil {
+ desktop := NewDesktop()
+ if err := app.Run(desktop); err != nil {
log.Fatal(err)
}
}
diff --git a/cmd/compositor/panel.go b/cmd/compositor/panel.go
new file mode 100644
index 000000000..da72c3ba7
--- /dev/null
+++ b/cmd/compositor/panel.go
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2025 Manjeet Singh .
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "image"
+ "image/color"
+
+ "rlxos.dev/pkg/graphics/canvas"
+ "rlxos.dev/pkg/graphics/event"
+ "rlxos.dev/pkg/graphics/widget"
+)
+
+type PanelPosition int
+
+const (
+ PanelPositionTop PanelPosition = iota
+ PanelPositionBottom
+)
+
+type Panel struct {
+ widget.BaseWidget
+
+ Position PanelPosition
+ StartButton widget.Widget
+ BackgroundColor color.Color
+ Color color.Color
+ Padding int
+ Size int
+}
+
+func NewPanel(position PanelPosition) *Panel {
+ p := &Panel{
+ Position: position,
+ StartButton: &widget.Button{
+ Label: "Start",
+ },
+ }
+
+ return p
+}
+
+func (p *Panel) SetBounds(rect image.Rectangle) {
+ switch p.Position {
+ case PanelPositionBottom:
+ p.BaseWidget.SetBounds(image.Rect(rect.Min.X, rect.Min.Y, rect.Max.X, p.Size))
+ case PanelPositionTop:
+ p.BaseWidget.SetBounds(image.Rect(rect.Min.X, rect.Max.Y-p.Size, rect.Max.X, rect.Max.Y))
+ }
+
+ p.StartButton.SetBounds(image.Rect(rect.Min.X, rect.Min.Y, p.Size-p.Padding, p.Size-p.Padding))
+}
+
+func (p *Panel) Draw(c canvas.Canvas) {
+ c.SetColor(p.BackgroundColor)
+ c.FillRect(p.Bounds)
+
+ p.StartButton.Draw(c)
+}
+func (p *Panel) Update(e event.Event) {
+ p.StartButton.Update(e)
+}
diff --git a/pkg/graphics/app/app.go b/pkg/graphics/app/app.go
index aa78fe84a..291f8badb 100644
--- a/pkg/graphics/app/app.go
+++ b/pkg/graphics/app/app.go
@@ -20,6 +20,7 @@ package app
import (
"fmt"
"log"
+ "time"
"rlxos.dev/pkg/graphics/driver"
"rlxos.dev/pkg/graphics/event"
@@ -27,7 +28,7 @@ import (
)
func Run(w widget.Widget, drivers ...driver.Driver) error {
- if drivers == nil {
+ if len(drivers) == 0 {
drivers = supportedDrivers
}
@@ -55,7 +56,8 @@ func Run(w widget.Widget, drivers ...driver.Driver) error {
running := true
for running {
- for _, ev := range selectedDriver.PollEvent() {
+ events := selectedDriver.PollEvent()
+ for _, ev := range events {
switch ev := ev.(type) {
case error:
log.Println("ERROR", err)
@@ -70,6 +72,10 @@ func Run(w widget.Widget, drivers ...driver.Driver) error {
if w.IsDamaged() {
surface.Update()
}
+
+ if len(events) == 0 {
+ time.Sleep(10 * time.Millisecond)
+ }
}
return nil
diff --git a/pkg/graphics/canvas/bitmap/bitmap.go b/pkg/graphics/canvas/bitmap/bitmap.go
index 62a442b87..5e6eda569 100644
--- a/pkg/graphics/canvas/bitmap/bitmap.go
+++ b/pkg/graphics/canvas/bitmap/bitmap.go
@@ -20,7 +20,6 @@ package bitmap
import (
"image"
"image/color"
- "image/draw"
)
type Canvas struct {
@@ -40,104 +39,114 @@ func (c *Canvas) Bounds() image.Rectangle {
}
func (c *Canvas) At(x, y int) color.Color {
- return c.BGRAt(x, y)
- // switch c.Model {
- // case ARGBModel:
- // return c.ARGBAt(x, y)
- // case BGRAModel:
- // return c.BGRAAt(x, y)
- // case BGRModel:
- // return c.BGRAt(x, y)
- // }
- // panic("unsupported model")
-}
-
-func (c *Canvas) BGRAAt(x, y int) BGRA {
- if !(image.Point{x, y}.In(c.Rect)) {
- return BGRA{}
- }
- i := c.PixOffset(x, y)
- s := c.Buffer[i : i+4 : i+4]
- return BGRA{s[0], s[1], s[2], s[3]}
-}
-
-func (c *Canvas) BGRAt(x, y int) BGR {
- if !(image.Point{x, y}.In(c.Rect)) {
- return BGR{}
+ offset := c.PixOffset(x, y)
+ if offset < 0 {
+ return color.Black
}
- i := c.PixOffset(x, y)
- s := c.Buffer[i : i+3 : i+3]
- return BGR{s[0], s[1], s[2]}
-}
-func (c *Canvas) ARGBAt(x, y int) ARGB {
- if !(image.Point{x, y}.In(c.Rect)) {
- return ARGB{}
+ switch c.Model {
+ case ARGBModel:
+ return ARGB{c.Buffer[offset], c.Buffer[offset+1], c.Buffer[offset+2], c.Buffer[offset+3]}
+ case BGRAModel:
+ return BGRA{c.Buffer[offset], c.Buffer[offset+1], c.Buffer[offset+2], c.Buffer[offset+3]}
+ case RGBModel:
+ return RGB{c.Buffer[offset], c.Buffer[offset+1], c.Buffer[offset+2]}
+ default: // BGR
+ return BGR{c.Buffer[offset], c.Buffer[offset+1], c.Buffer[offset+2]}
}
- i := c.PixOffset(x, y)
- s := c.Buffer[i : i+4 : i+4]
- return ARGB{s[0], s[1], s[2], s[3]}
}
func (c *Canvas) PixOffset(x, y int) int {
- return (y-c.Rect.Min.Y)*c.Stride + (x-c.Rect.Min.X)*4
+ if !(image.Point{X: x, Y: y}.In(c.Rect)) {
+ return -1
+ }
+ bytesPerPixel := 3
+ if c.Model == ARGBModel || c.Model == BGRAModel {
+ bytesPerPixel = 4
+ }
+ return (y-c.Rect.Min.Y)*c.Stride + (x-c.Rect.Min.X)*bytesPerPixel
}
func (c *Canvas) Set(x, y int, clr color.Color) {
- if !(image.Point{x, y}.In(c.Rect)) {
+ offset := c.PixOffset(x, y)
+ if offset < 0 {
return
}
- i := c.PixOffset(x, y)
- c1 := BGRModel.Convert(clr).(BGR)
- s := c.Buffer[i : i+3 : i+3]
- s[0] = c1.B
- s[1] = c1.G
- s[2] = c1.R
-
- // switch c.Model {
- // case ARGBModel:
- // c1 := ARGBModel.Convert(clr).(ARGB)
- // s := c.Buffer[i : i+4 : i+4]
- // s[0] = c1.A
- // s[1] = c1.R
- // s[2] = c1.G
- // s[3] = c1.B
- // case BGRAModel:
- // c1 := BGRAModel.Convert(clr).(BGRA)
- // s := c.Buffer[i : i+4 : i+4]
- // s[0] = c1.B
- // s[1] = c1.G
- // s[2] = c1.R
- // s[3] = c1.A
-
- // case BGRModel:
- // c1 := BGRModel.Convert(clr).(BGR)
- // s := c.Buffer[i : i+3 : i+3]
- // s[0] = c1.B
- // s[1] = c1.G
- // s[2] = c1.R
- // default:
- // log.Fatalf("unsupported format %v", c.Model)
- // }
-
+ switch c.Model {
+ case ARGBModel:
+ c1 := ARGBModel.Convert(clr).(ARGB)
+ copy(c.Buffer[offset:offset+4], []byte{c1.A, c1.R, c1.G, c1.B})
+ case BGRAModel:
+ c1 := BGRAModel.Convert(clr).(BGRA)
+ copy(c.Buffer[offset:offset+4], []byte{c1.B, c1.G, c1.R, c1.A})
+ case RGBModel:
+ c1 := RGBModel.Convert(clr).(RGB)
+ copy(c.Buffer[offset:offset+3], []byte{c1.R, c1.G, c1.B})
+ default: // BGR
+ c1 := BGRModel.Convert(clr).(BGR)
+ copy(c.Buffer[offset:offset+3], []byte{c1.B, c1.G, c1.R})
+ }
}
func (c *Canvas) SetColor(clr color.Color) {
c.color = clr
}
-func (c *Canvas) DrawRect(rectangle image.Rectangle) {
-
+func (c *Canvas) DrawRect(rect image.Rectangle) {
+ c.DrawLine(rect.Min, image.Pt(rect.Max.X-1, rect.Min.Y))
+ c.DrawLine(image.Pt(rect.Max.X-1, rect.Min.Y), image.Pt(rect.Max.X-1, rect.Max.Y-1))
+ c.DrawLine(image.Pt(rect.Max.X-1, rect.Max.Y-1), image.Pt(rect.Min.X, rect.Max.Y-1))
+ c.DrawLine(image.Pt(rect.Min.X, rect.Max.Y-1), rect.Min)
}
func (c *Canvas) DrawPixels(pixels ...image.Point) {
-
+ for _, p := range pixels {
+ c.Set(p.X, p.Y, c.color)
+ }
}
func (c *Canvas) DrawLine(start, end image.Point) {
+ dx := abs(end.X - start.X)
+ dy := -abs(end.Y - start.Y)
+ sx := 1
+ if start.X > end.X {
+ sx = -1
+ }
+ sy := 1
+ if start.Y > end.Y {
+ sy = -1
+ }
+ err := dx + dy
+
+ for {
+ c.Set(start.X, start.Y, c.color)
+ if start.X == end.X && start.Y == end.Y {
+ break
+ }
+ e2 := 2 * err
+ if e2 >= dy {
+ err += dy
+ start.X += sx
+ }
+ if e2 <= dx {
+ err += dx
+ start.Y += sy
+ }
+ }
+}
+func (c *Canvas) FillRect(rect image.Rectangle) {
+ rect = rect.Intersect(c.Rect)
+ for y := rect.Min.Y; y < rect.Max.Y; y++ {
+ for x := rect.Min.X; x < rect.Max.X; x++ {
+ c.Set(x, y, c.color)
+ }
+ }
}
-func (c *Canvas) FillRect(rectangle image.Rectangle) {
- draw.Draw(c, rectangle, &image.Uniform{C: c.color}, image.Point{}, draw.Src)
+func abs(n int) int {
+ if n < 0 {
+ return -n
+ }
+ return n
}
diff --git a/pkg/graphics/canvas/bitmap/font.go b/pkg/graphics/canvas/bitmap/font.go
index 0290d45c2..7a6618cb1 100644
--- a/pkg/graphics/canvas/bitmap/font.go
+++ b/pkg/graphics/canvas/bitmap/font.go
@@ -11,8 +11,101 @@
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see >(7-col))&1 != 0 {
+ for sx := 0; sx < scale; sx++ {
+ for sy := 0; sy < scale; sy++ {
+ c.Set(x+col*scale+sx, y+row*scale+sy, c.color)
+ }
+ }
+ }
+ }
+ }
+ x += int(fontHeader.width) * scale
+ }
+}
diff --git a/pkg/graphics/canvas/bitmap/font.psf b/pkg/graphics/canvas/bitmap/font.psf
new file mode 100644
index 000000000..3e67693f5
Binary files /dev/null and b/pkg/graphics/canvas/bitmap/font.psf differ
diff --git a/pkg/graphics/canvas/bitmap/rgb.go b/pkg/graphics/canvas/bitmap/rgb.go
new file mode 100644
index 000000000..44109637f
--- /dev/null
+++ b/pkg/graphics/canvas/bitmap/rgb.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2025 Manjeet Singh .
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bitmap
+
+import "image/color"
+
+type RGB struct {
+ R, G, B uint8
+}
+
+func (c RGB) RGBA() (r, g, b, a uint32) {
+ r = uint32(c.R)
+ r |= r << 8
+ g = uint32(c.G)
+ g |= g << 8
+ b = uint32(c.B)
+ b |= b << 8
+ return
+}
+
+var RGBModel color.Model = color.ModelFunc(func(c color.Color) color.Color {
+ if _, ok := c.(RGB); ok {
+ return c
+ }
+ r, g, b, _ := c.RGBA()
+ return RGB{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8)}
+})
diff --git a/pkg/graphics/canvas/canvas.go b/pkg/graphics/canvas/canvas.go
index d194e9e63..ba4edea8c 100644
--- a/pkg/graphics/canvas/canvas.go
+++ b/pkg/graphics/canvas/canvas.go
@@ -27,5 +27,8 @@ type Canvas interface {
DrawRect(rectangle image.Rectangle)
DrawPixels(pixels ...image.Point)
DrawLine(start, end image.Point)
+ FontSize(text string, size int) (height, width int)
+ FontSizeInBounds(text string, bounds image.Rectangle) int
+ DrawText(text string, size int, pos image.Point)
FillRect(rectangle image.Rectangle)
}
diff --git a/pkg/graphics/driver/fbdev/driver.go b/pkg/graphics/driver/fbdev/driver.go
index 9f2b887cc..390e6a97b 100644
--- a/pkg/graphics/driver/fbdev/driver.go
+++ b/pkg/graphics/driver/fbdev/driver.go
@@ -21,6 +21,7 @@ import (
"bytes"
"encoding/binary"
"image"
+ "image/color"
"log"
"os"
"syscall"
@@ -37,7 +38,6 @@ type Driver struct {
Device string
screenInfo FixScreenInfo
file *os.File
- tty *os.File
mousePosition image.Point
inputDevices []int
epoll int
@@ -57,8 +57,9 @@ func (d *Driver) Init() error {
}
log.Printf("Got framebuffer at %x of size %d", d.screenInfo.Address, d.screenInfo.Size)
- // TODO: is it correct way to enable and disable cursor
- os.WriteFile("/sys/class/graphics/fbcon/cursor_blink", []byte("0"), 0)
+ if err := os.WriteFile("/sys/class/graphics/fbcon/cursor_blink", []byte("0"), 0); err != nil {
+ log.Println("Warning: Failed to disable cursor:", err)
+ }
if err := d.OpenInputDevices(); err != nil {
_ = d.file.Close()
return err
@@ -70,8 +71,8 @@ func (d *Driver) Init() error {
func (d *Driver) PollEvent() []event.Event {
epollEvent := make([]syscall.EpollEvent, 20)
n, err := syscall.EpollWait(d.epoll, epollEvent, -1)
- if err != nil {
- return []event.Event{err}
+ if n < 0 || err != nil {
+ return nil
}
var events []event.Event
@@ -145,13 +146,13 @@ func (d *Driver) PollEvent() []event.Event {
}
func (d *Driver) Destroy() {
- // TODO: is it correct way to enable and disable cursor
- os.WriteFile("/sys/class/graphics/fbcon/cursor_blink", []byte("1"), 0)
+ if err := os.WriteFile("/sys/class/graphics/fbcon/cursor_blink", []byte("1"), 0); err != nil {
+ log.Println("Warning: Failed to enable cursor:", err)
+ }
for _, d := range d.inputDevices {
- syscall.Close(d)
+ _ = syscall.Close(d)
}
- syscall.Close(d.epoll)
- d.tty.Close()
+ _ = syscall.Close(d.epoll)
if err := d.file.Close(); err != nil {
log.Println(err)
}
@@ -172,6 +173,7 @@ func (d *Driver) AddSurface(w widget.Widget) (surface.Surface, error) {
}
log.Printf("Screen Resolution %dx%d@%d", screenInfo.XRes, screenInfo.YRes, screenInfo.BitsPerPixel)
+ log.Printf("Screen Virtual Resolution %dx%d,%dx%d", screenInfo.XOffset, screenInfo.YOffset, screenInfo.XResVirtual, screenInfo.YResVirtual)
log.Printf("Red: offset=%d, length=%d", screenInfo.Red.Offset, screenInfo.Red.Length)
log.Printf("Green: offset=%d, length=%d", screenInfo.Green.Offset, screenInfo.Green.Length)
log.Printf("Blue: offset=%d, length=%d", screenInfo.Blue.Offset, screenInfo.Blue.Length)
@@ -179,24 +181,33 @@ func (d *Driver) AddSurface(w widget.Widget) (surface.Surface, error) {
d.mousePosition = image.Pt(int(screenInfo.XRes)/2, int(screenInfo.YRes)/2)
- screenSize := screenInfo.XRes * screenInfo.YRes * screenInfo.BitsPerPixel / 8
-
- buffer, err := syscall.Mmap(int(d.file.Fd()), 0, int(screenSize), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
+ buffer, err := syscall.Mmap(int(d.file.Fd()), 0, int(d.screenInfo.Size), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
return nil, err
}
+ var colorModel color.Model
+ switch {
+ case screenInfo.Red.Offset == 16 && screenInfo.Green.Offset == 8 && screenInfo.Blue.Offset == 0:
+ colorModel = bitmap.BGRModel
+ case screenInfo.Red.Offset == 0 && screenInfo.Green.Offset == 8 && screenInfo.Blue.Offset == 16:
+ colorModel = bitmap.RGBModel
+ default:
+ colorModel = bitmap.BGRModel
+ }
+
s := &Surface{
canvas: &bitmap.Canvas{
Buffer: buffer,
Stride: int(d.screenInfo.LineLength),
Rect: image.Rect(int(screenInfo.XOffset), int(screenInfo.YOffset), int(screenInfo.XRes), int(screenInfo.YRes)),
- // TODO: set color model based on screenInfo.Color.Offset
- Model: bitmap.BGRModel,
+ Model: colorModel,
},
driver: d,
widget: w,
}
+ log.Printf("Framebuffer Bounds %v", s.canvas.Bounds())
+
w.SetBounds(s.canvas.Bounds())
s.Update()
return s, nil
diff --git a/pkg/graphics/theme/theme.go b/pkg/graphics/theme/theme.go
index 2d965b259..ca9dbb076 100644
--- a/pkg/graphics/theme/theme.go
+++ b/pkg/graphics/theme/theme.go
@@ -11,8 +11,97 @@
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * along with this program. If not, see 100 {
+ percent = 100
+ }
+
+ r, g, b, a := c.RGBA()
+ factor := float64(percent) / 100.0
+
+ newR := uint8(min(255, int(float64(r>>8)+(float64(255-(r>>8))*factor))))
+ newG := uint8(min(255, int(float64(g>>8)+(float64(255-(g>>8))*factor))))
+ newB := uint8(min(255, int(float64(b>>8)+(float64(255-(b>>8))*factor))))
+
+ return color.RGBA{R: newR, G: newG, B: newB, A: uint8(a >> 8)}
+}
diff --git a/pkg/graphics/widget/button.go b/pkg/graphics/widget/button.go
index 96fc8c47d..f41af647c 100644
--- a/pkg/graphics/widget/button.go
+++ b/pkg/graphics/widget/button.go
@@ -20,6 +20,7 @@ package widget
import (
"image/color"
"log"
+ "rlxos.dev/pkg/graphics/theme"
"rlxos.dev/pkg/graphics/canvas"
"rlxos.dev/pkg/graphics/event"
@@ -27,20 +28,47 @@ import (
type Button struct {
BaseWidget
+ Label string
+ Color color.Color
BackgroundColor color.Color
- HoverColor color.Color
- OnClick func()
+
+ HoverColor color.Color
+ HoverBackgroundColor color.Color
+
+ OnClick func()
isHovered bool
}
+func NewButton(label string) *Button {
+ b := &Button{
+ Label: label,
+ Color: theme.Active.OnPrimaryContainer,
+ BackgroundColor: theme.Active.PrimaryContainer,
+
+ HoverColor: theme.Lighten(theme.Active.OnPrimaryContainer, 10),
+ HoverBackgroundColor: theme.Lighten(theme.Active.PrimaryContainer, 10),
+ }
+
+ return b
+}
+
func (b *Button) Draw(c canvas.Canvas) {
if b.isHovered {
- c.SetColor(b.HoverColor)
+ c.SetColor(b.HoverBackgroundColor)
} else {
c.SetColor(b.BackgroundColor)
}
c.FillRect(b.Bounds)
+
+ if b.isHovered {
+ c.SetColor(b.HoverColor)
+ } else {
+ c.SetColor(b.Color)
+ }
+ size := c.FontSizeInBounds(b.Label, b.Bounds)
+ log.Println("Font size:", size)
+ c.DrawText(b.Label, size, b.Bounds.Min)
}
func (b *Button) Update(ev event.Event) {
@@ -49,12 +77,13 @@ func (b *Button) Update(ev event.Event) {
isHovering := ev.Point.In(b.Bounds)
if isHovering != b.isHovered {
log.Println("Hovered changed")
+ b.isHovered = isHovering
b.Damaged = true
}
- b.isHovered = isHovering
case event.MouseButton:
if b.isHovered && ev.State == 1 && b.OnClick != nil {
b.OnClick()
+ b.Damaged = true
}
}
}
diff --git a/pkg/graphics/widget/container.go b/pkg/graphics/widget/container.go
index 7d7d7b574..42dc7e4a3 100644
--- a/pkg/graphics/widget/container.go
+++ b/pkg/graphics/widget/container.go
@@ -1,24 +1,9 @@
-/*
- * Copyright (c) 2025 Manjeet Singh .
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
package widget
import (
"image"
+ "image/color"
+ "rlxos.dev/pkg/graphics/theme"
"rlxos.dev/pkg/graphics/canvas"
"rlxos.dev/pkg/graphics/event"
@@ -34,39 +19,55 @@ const (
type Container struct {
BaseWidget
+ BackgroundColor color.Color
+ Color color.Color
+
Children []Widget
Orientation Orientation
Spacing int
}
+func NewContainer(orientation Orientation, spacing int) *Container {
+ c := &Container{
+ BackgroundColor: theme.Active.BackgroundColor,
+ Color: theme.Active.OnBackgroundColor,
+ Orientation: orientation,
+ Spacing: spacing,
+ }
+
+ return c
+}
+
func (c *Container) SetBounds(bounds image.Rectangle) {
c.BaseWidget.SetBounds(bounds)
- if len(c.Children) == 0 {
+ childCount := len(c.Children)
+ if childCount == 0 {
return
}
- totalSpacing := (len(c.Children) - 1) * c.Spacing
+ totalSpacing := (childCount - 1) * c.Spacing
+
if c.Orientation == Vertical {
- childHeight := (bounds.Dy() - totalSpacing) / len(c.Children)
+ childHeight := (bounds.Dy() - totalSpacing) / childCount
y := bounds.Min.Y
for _, child := range c.Children {
- childBounds := image.Rect(bounds.Min.X, y, bounds.Max.X, y+childHeight)
- child.SetBounds(childBounds)
+ child.SetBounds(image.Rect(bounds.Min.X, y, bounds.Max.X, y+childHeight))
y += childHeight + c.Spacing
}
} else {
- childWidth := (bounds.Dx() - totalSpacing) / len(c.Children)
+ childWidth := (bounds.Dx() - totalSpacing) / childCount
x := bounds.Min.X
for _, child := range c.Children {
- childBounds := image.Rect(x, bounds.Min.Y, x+childWidth, bounds.Max.Y)
- child.SetBounds(childBounds)
+ child.SetBounds(image.Rect(x, bounds.Min.Y, x+childWidth, bounds.Max.Y))
x += childWidth + c.Spacing
}
}
}
func (c *Container) Draw(canvas canvas.Canvas) {
+ canvas.SetColor(c.BackgroundColor)
+ canvas.FillRect(c.Bounds)
for _, child := range c.Children {
child.Draw(canvas)
}
diff --git a/pkg/graphics/widget/label.go b/pkg/graphics/widget/label.go
new file mode 100644
index 000000000..0a1172ac5
--- /dev/null
+++ b/pkg/graphics/widget/label.go
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2025 Manjeet Singh .
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package widget
+
+import (
+ "image/color"
+ "rlxos.dev/pkg/graphics/canvas"
+ "rlxos.dev/pkg/graphics/event"
+ "rlxos.dev/pkg/graphics/theme"
+)
+
+type Label struct {
+ BaseWidget
+
+ Color color.Color
+ Text string
+}
+
+func NewLabel(text string) *Label {
+ return &Label{
+ Color: theme.Active.OnPrimaryColor,
+ Text: text,
+ }
+}
+
+func (l *Label) Draw(c canvas.Canvas) {
+ c.SetColor(l.Color)
+ size := c.FontSizeInBounds(l.Text, l.Bounds)
+ c.DrawText(l.Text, size, l.Bounds.Min)
+}
+
+func (l *Label) Update(e event.Event) {
+
+}