From fee6e1e105e21f8d43f453be1bbd7e3f57d01411 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sat, 1 Jan 2022 18:43:36 -0800 Subject: [PATCH] New widget: ColorPicker, plus other changes New properties are added to EventData for Supervisor events: * Widget: a reference to the widget which is receiving the event. * Clicked (bool): for MouseMove events records if the primary button is pressed. * func RelativePoint(): returns a version of EventData.Point adjusted to be relative to the Widget (0,0 at the Widget's absolute position on screen). Other changes: * Destroy() method for the Widget interface: widgets that need to free up resources on teardown should define this, the BaseWidget provides a no-op implementation. * Window.Resize() will properly resize a Window. * Window.Center(w, h int) to easily center a window on screen. --- README.md | 7 + colorpicker.go | 585 ++++++++++++++++++++++++++++++++++ eg/README.md | 3 +- eg/colorpicker/README.md | 20 ++ eg/colorpicker/main.go | 95 ++++++ eg/colorpicker/screenshot.png | Bin 0 -> 26756 bytes go.mod | 4 +- go.sum | 4 + supervisor.go | 57 +++- tabframe.go | 7 + widget.go | 6 + window.go | 19 ++ window_manager.go | 3 + 13 files changed, 792 insertions(+), 18 deletions(-) create mode 100644 colorpicker.go create mode 100644 eg/colorpicker/README.md create mode 100644 eg/colorpicker/main.go create mode 100644 eg/colorpicker/screenshot.png diff --git a/README.md b/README.md index 1b0ebc2..ae2d0e2 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,13 @@ most complex. * [x] **SelectBox**: a kind of MenuButton that lets the user choose a value from a list of possible values. +Some useful helper widgets: + +* **ColorPicker**: a ui.Window popup that lets the user choose a color value. + It shows a graphical gradient they can click on and an ability to enter a + custom hexadecimal value by hand (needs assistance from your program). + [Example](eg/colorpicker) + **Work in progress widgets:** * [ ] **Scrollbar**: a Frame including a trough, scroll buttons and a diff --git a/colorpicker.go b/colorpicker.go new file mode 100644 index 0000000..c70198a --- /dev/null +++ b/colorpicker.go @@ -0,0 +1,585 @@ +package ui + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "image" + "image/png" + "io/ioutil" + + "git.kirsle.net/go/render" +) + +// ColorPicker is a Window that allows the user to pick out a color. +type ColorPicker struct { + *Window + + // Config settings. + Title string + Color render.Color // initial color selection + Supervisor *Supervisor + Engine render.Engine + + // Callback function in case the user wants to manually enter a hex color code. + // Your program should prompt them by any means and return their chosen color. + // Return a zero color (render.Invisible) to mean cancel. + OnManualInput func(callback func(render.Color)) + + then func(render.Color) // .Then() callback + cancel func() // .OnCancel() callback + selected render.Color + + // SDL2 etc. structures that will need freed. + tex render.Texturer +} + +// DefaultColorPickerSize sets the default window size used in NewColorPicker +// unless the user specified overrides. +var DefaultColorPickerSize = render.Rect{W: 240, H: 190} + +// NewColorPicker creates a new ColorPicker window. Specify the dimensions +// you want the window to appear in (width, height int) to specify a desired +// window size or else the default will be DefaultColorPickerSize. +func NewColorPicker(config ColorPicker, dimensions ...int) (*ColorPicker, error) { + var size = DefaultColorPickerSize + if len(dimensions) == 2 { + size = render.NewRect(dimensions[0], dimensions[1]) + } + + // Validate settings. + if config.Title == "" { + config.Title = "Select a color" + } + if config.Supervisor == nil { + return nil, errors.New("a ui.Supervisor is required") + } + if config.Engine == nil { + return nil, errors.New("a render.Engine is required") + } + + // Create the colorpicker window. + window := NewWindow(config.Title) + window.Resize(size) + window.SetButtons(CloseButton) + + w := &ColorPicker{ + Title: config.Title, + Window: window, + Supervisor: config.Supervisor, + Engine: config.Engine, + Color: config.Color, + OnManualInput: config.OnManualInput, + } + + window.Handle(CloseWindow, func(ed EventData) error { + if w.cancel != nil { + w.cancel() + } + return nil + }) + + if err := w.setup(); err != nil { + return nil, err + } + + w.Window.Supervise(w.Supervisor) + w.Window.Hide() + return w, nil +} + +// Then is a callback function when the user has chosen a color. +func (w *ColorPicker) Then(callback func(render.Color)) { + w.then = callback +} + +// OnCancel is a callback to handle the ColorPicker being dismissed by the user. +func (w *ColorPicker) OnCancel(callback func()) { + w.cancel = callback +} + +// setup the main UI of the ColorPicker window. +func (w *ColorPicker) setup() error { + // Load the color gradient image. Guaranteed not to error. + if tex, err := w.Engine.StoreTexture("ui.ColorPicker/spectrum.png", MakeColorPickerGradient()); err != nil { + return err + } else { + w.tex = tex + } + + // Prepare values for the currently selected color. + var ( + OriginalColor = w.Color + CurrentColor = w.Color + ) + if OriginalColor.IsZero() { + OriginalColor = render.Black + } + CurrentColor = OriginalColor + w.selected = CurrentColor + + // Divide up the main frames. + var ( + btnFrame = NewFrame("Buttons") + frame = NewFrame("Main Frame") + leftFrame = NewFrame("Content Frame") + ) + + // DEBUG + // btnFrame.SetBackground(render.Yellow) + // frame.SetBackground(render.Red) + // leftFrame.SetBackground(render.Green) + + // The main frame vs. the button frame on bottom. + w.Pack(frame, Pack{ + Side: N, + Fill: true, + Expand: true, + }) + w.Pack(btnFrame, Pack{ + Side: N, + PadY: 4, + }) + + // The left and right frames of the main frame. + frame.Pack(leftFrame, Pack{ + Side: N, + Fill: true, + Expand: true, + }) + + //////// + // Buttons frame. + { + for _, config := range []struct { + label string + callback func() + }{ + {"Ok", func() { + w.Destroy() + if w.then != nil { + w.then(w.selected) + } + }}, + {"Cancel", func() { + if w.cancel != nil { + w.cancel() + } + w.Destroy() + }}, + } { + config := config + + btn := NewButton(config.label, NewLabel(Label{ + Text: config.label, + Font: DefaultFont.Update(render.Text{ + PadX: 8, + PadY: 2, + }), + })) + btn.Handle(Click, func(ed EventData) error { + config.callback() + return nil + }) + w.Supervisor.Add(btn) + btnFrame.Pack(btn, Pack{ + Side: W, + PadX: 2, + }) + } + } + + ///////// + // Left frame. + { + // The gradient image up top. + gradient, _ := ImageFromImage(MakeColorPickerGradient()) + leftFrame.Pack(gradient, Pack{ + Side: N, + PadX: 2, + PadY: 4, + }) + + // Below that: your Current Color | Original Color indicators. + var ( + previewRow = NewFrame("Color Preview") + curColorFrame = NewFrame("Current Color") + origColorFrame = NewFrame("Original Color") + previewDividerFrame = NewFrame("Divider") + hexLabel = NewLabel(Label{ + Text: " Hex color:", + Font: DefaultFont, + }) + hexButton = NewButton("Hex Button", NewLabel(Label{ + Text: w.selected.ToHex(), + Font: DefaultFont.Update(render.Text{ + PadX: 6, + }), + })) + ) + curColorFrame.Configure(Config{ + Width: 24, + Height: 24, + Background: CurrentColor, + BorderStyle: BorderSunken, + BorderSize: 2, + BorderColor: CurrentColor, + }) + origColorFrame.Configure(Config{ + Width: 24, + Height: 24, + Background: OriginalColor, + BorderColor: OriginalColor, + BorderSize: 2, + BorderStyle: BorderSunken, + }) + previewDividerFrame.Configure(Config{ + Width: 1, + Height: 24, + BorderStyle: BorderRaised, + BorderSize: 2, + BorderColor: render.Grey, + }) + previewRow.Pack(curColorFrame, Pack{ + Side: W, + PadX: 4, + }) + previewRow.Pack(previewDividerFrame, Pack{ + Side: W, + }) + previewRow.Pack(origColorFrame, Pack{ + Side: W, + PadX: 4, + }) + previewRow.Pack(hexLabel, Pack{ + Side: W, + }) + previewRow.Pack(hexButton, Pack{ + Side: W, + PadX: 4, + }) + leftFrame.Pack(previewRow, Pack{ + Side: N, + FillX: true, + PadX: 5, + }) + + // Bind clicks into the spectrum image to update the selected color. + gradient.Handle(MouseMove, func(ed EventData) error { + if ed.Clicked { + point := ed.RelativePoint() + + color := render.FromColor(gradient.Image.At(point.X, point.Y)) + + w.selected = color + hexButton.SetText(color.ToHex()) + curColorFrame.Configure(Config{ + Background: color, + BorderColor: color, + }) + } + return nil + }) + + // Clicking the original color resets it. + origColorFrame.Handle(Click, func(ed EventData) error { + color := OriginalColor + w.selected = color + hexButton.SetText(color.ToHex()) + curColorFrame.Configure(Config{ + Background: color, + BorderColor: color, + }) + return nil + }) + + // Clicking the Hex Button prompts the user to enter a hex code themselves. + hexButton.Handle(Click, func(ed EventData) error { + if w.OnManualInput != nil { + w.OnManualInput(func(color render.Color) { + if color.IsZero() { + return + } + + w.selected = color + hexButton.SetText(color.ToHex()) + curColorFrame.Configure(Config{ + Background: color, + BorderColor: color, + }) + }) + } + return nil + }) + + w.Supervisor.Add(gradient) + w.Supervisor.Add(origColorFrame) + w.Supervisor.Add(hexButton) + } + + return nil +} + +// Compute the widget. +func (w *ColorPicker) Compute(e render.Engine) { + // TODO: free the w.tex +} + +// Destroy the ColorPicker widget. Call this instead of Hide() if you close the +// widget programmatically! It will free up SDL textures and so on. +func (w *ColorPicker) Destroy() { + w.Hide() +} + +// ColorPickerPreset shows the preset color buttons. +// Suggested to have 18 colors which is displayed in 2 rows of 9 buttons. +// More presets may need larger than default window size to fit. +var ColorPickerPreset = []string{ + // Row 1 + "#FFFFFF", // White + "#CCCCCC", // Light grey + "#FF0000", // Red + "#FF9900", // Orange + "#FFFF00", // Yellow + "#00FF00", // Lime green + "#00FFFF", // Cyan + "#FF00FF", // Magenta + "#FF9999", // Pastel red + "#99FF99", // Pastel green + + // Row 2 + "#000000", // Black + "#999999", // Dark grey + "#990000", // Dark red + "#996600", // Brown + "#999900", // Gold + "#009900", // Green + "#009999", // Teal + "#000099", // Dark Blue + "#990099", // Purple + "#9999FF", // Pastel blue + "#FFFF99", // Pastel yellow +} + +// Load the color spectrum image. +func MakeColorPickerGradient() image.Image { + data, err := base64.StdEncoding.DecodeString(colorPickerGradient) + if err != nil { + fmt.Printf("ui.MakeColorPickerGradient: %s", err) + return nil + } + + // debug + ioutil.WriteFile("gradient.png", data, 0644) + + image, err := png.Decode(bytes.NewBuffer(data)) + if err != nil { + fmt.Printf("ui.MakeColorPickerGradient: png.Decode: %s", err) + return nil + } + + return image +} + +// colorPickerGradient holds Base64 PNG image data for the color picker graphic. +const colorPickerGradient = `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 +aiCrcAAAAABJRU5ErkJggg==` diff --git a/eg/README.md b/eg/README.md index 5d80753..d10c3fc 100644 --- a/eg/README.md +++ b/eg/README.md @@ -15,4 +15,5 @@ screenshot and description: * [Themes](themes/): a UI demo that shows off the Default, Flat, and Dark UI themes as part of experimental theming support. * [TabFrame](tabframe/): demo for the TabFrame widget showing multiple Windows - with tabbed interfaces. \ No newline at end of file + with tabbed interfaces. +* [ColorPicker](colorpicker/): demo for the ColorPicker widget. diff --git a/eg/colorpicker/README.md b/eg/colorpicker/README.md new file mode 100644 index 0000000..cd46386 --- /dev/null +++ b/eg/colorpicker/README.md @@ -0,0 +1,20 @@ +# ColorPicker Demo + +![Screenshot](screenshot.png) + +This demo shows off the ColorPicker window. + +The ColorPicker lets you ask the user to select a color, visually, +using a callback interface. While the UI toolkit doesn't support +text input entry, there is a work-around to prompt the user to enter +a color by hex code with assistance of your program. + +In this example program, clicking on the Hex color button will prompt +you via STDIN (check the terminal window!) to enter a color value. The +UI will be frozen until your answer is given. For programs that don't +need this, the Hex color button does nothing when clicked. + +## Running It + +From your terminal, just type `go run main.go` from this +example's directory. diff --git a/eg/colorpicker/main.go b/eg/colorpicker/main.go new file mode 100644 index 0000000..915b68c --- /dev/null +++ b/eg/colorpicker/main.go @@ -0,0 +1,95 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "git.kirsle.net/go/render" + "git.kirsle.net/go/render/sdl" + "git.kirsle.net/go/ui" +) + +var WindowColor = render.SkyBlue + +func init() { + sdl.DefaultFontFilename = "../DejaVuSans.ttf" +} + +func main() { + mw, err := ui.NewMainWindow("ColorPicker Demo", 812, 375) + if err != nil { + panic(err) + } + + mw.SetBackground(WindowColor) + + btn := ui.NewButton("Test", ui.NewLabel(ui.Label{ + Text: "Pick the background color", + Font: render.Text{ + Size: 32, + }, + })) + btn.Handle(ui.Click, func(ed ui.EventData) error { + colorpicker, err := ui.NewColorPicker(ui.ColorPicker{ + Title: "Select a background color", + Supervisor: mw.Supervisor(), + Engine: mw.Engine, + Color: WindowColor, + + // Until the UI toolkit has normal text entry controls, this work-around + // allows your application to ask the user to enter a hex color code + // themselves, using any means available. This is an asynchronous + // procedure where you are given a callback function to send your answer + // whenever you have it. For this example, look for the prompt + // question in your terminal window! + OnManualInput: func(callback func(render.Color)) { + // Prompt the user to enter a hex color in the terminal. + var s string + fmt.Fprintf(os.Stderr, "Enter a hexadecimal color code> ") + r := bufio.NewReader(os.Stdin) + for { + s, _ = r.ReadString('\n') + if s != "" { + break + } + } + + // Parse it as a color. + fmt.Printf("Answer: %s\n", s) + color, err := render.HexColor(strings.TrimSpace(s)) + if err != nil { + fmt.Printf("%s\n", err) + return + } + + // Ping the callback function with our answer. + callback(color) + }, + }) + if err != nil { + fmt.Printf("Error initializing colorpicker: %s\n", err) + return err + } + + colorpicker.Center(mw.Engine.WindowSize()) + colorpicker.Then(func(color render.Color) { + WindowColor = color + mw.SetBackground(WindowColor) + }) + colorpicker.OnCancel(func() { + fmt.Println("ColorPicker was dismissed by user") + }) + + fmt.Printf("Open ColorPicker: %+v\n", colorpicker) + colorpicker.Show() + return nil + }) + mw.Place(btn, ui.Place{ + Center: true, + Middle: true, + }) + + mw.MainLoop() +} diff --git a/eg/colorpicker/screenshot.png b/eg/colorpicker/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f8542d6c59366f5fd2a6d19f43729f153dcabe05 GIT binary patch literal 26756 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 w-JVh6LTXqSYJ4xe3%;t6tUU@B3H