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) { + +}