furex

package module
v2.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 18, 2023 License: BSD-3-Clause Imports: 16 Imported by: 0

README

Furex

Furex is a minimal GUI framework built on top of Ebitengine, a 2D game engine for Go. It provides support for the Flex Layout Algorithm, a powerful tool for laying out items of different sizes and dimensions. Furex is not a component library, but rather a framework for positioning and stacking virtual widgets, handling button and touch events for mobile, and more. How these widgets are rendered is up to the user.

Assets by Kenney. Fonts by 00ff.

Full source code of the example is here.

Contents

Motivation

Flexbox is a popular layout mechanism in web development, used for creating responsive and flexible user interfaces. With Furex, we can bring this same concept to game development using Go and Ebitengine. This library aims to make it easier for developers with experience in web or React projects to create dynamic, user-friendly game UI.

If you are not familiar with Flexbox Layout, you can learn about it at this website.

Features

Here are some of the key features of Furex:

  • Flexbox layout: The UI layout can be configured using the properties of View instances, which can be thought of as equivalent to DIV elements in HTML. These views can be stacked or nested to create complex layouts.

  • Custom widgets: View instances can receive a Handler which is responsible for drawing and updating the view. This allows users to create any type of UI component by implementing the appropriate handler interfaces, such as Drawer, Updater, and more.

  • Button support: To create a button, users can implement the ButtonHandler interface. This supports both touch and mouse input for button actions. See the Example Button for more details.

  • Touch and mouse events: Furex provides support for handling touch events and positions using the TouchHandler interface, and mouse click events using the MouseLeftButtonHandler interface. It also offers support for detecting mouse position events using the MouseHandler interface, and mouse enter/leave events using the MouseEnterLeaveHandler interface.

  • Swipe gestures: Users can detect swipe gestures by implementing the SwipeHandler interface.

These are just a few examples of the capabilities of Furex. For more information, be sure to check out the GoDoc documentation.

Getting Started

To get started with Furex, install Furex using the following command:

go get github.com/sedyh/furex/v2

Basic Usage

Here's a simple example of how to use Furex to create an UI in your game:

Full source code of the example

type Game struct {
  initOnce sync.Once
  screen   screen
  gameUI   *furex.View
}

func (g *Game) Update() error {
  g.initOnce.Do(func() {
    g.setupUI()
  })
  return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
  g.gameUI.Draw(screen)
}

func (g *Game) setupUI() {
  screen.Fill(color.RGBA{0x3d, 0x55, 0x0c, 0xff})
  colors := []color.Color{
    color.RGBA{0x3d, 0x55, 0x0c, 0xff},
    color.RGBA{0x81, 0xb6, 0x22, 0xff},
    color.RGBA{0xec, 0xf8, 0x7f, 0xff},
  }

  g.gameUI = &furex.View{
    Width:        g.screen.Width,
    Height:       g.screen.Height,
    Direction:    furex.Row,
    Justify:      furex.JustifyCenter,
    AlignItems:   furex.AlignItemCenter,
    AlignContent: furex.AlignContentCenter,
    Wrap:         furex.Wrap,
  }

  for i := 0; i < 20; i++ {
    g.gameUI.AddChild(&furex.View{
      Width:  100,
      Height: 100,
      Handler: &Box{
        Color: colors[i%len(colors)],
      },
    })
  }
}

type Box struct {
  Color color.Color
}

var _ furex.Drawer = (*Box)(nil)

func (b *Box) Draw(screen *ebiten.Image, frame Rectangle, view *furex.View) {
  graphic.FillRect(screen, &graphic.FillRectOpts{
    Rect: frame, Color: b.Color,
  })
}

Building UI with HTML

Sometimes making a complex UI tree in Go can be cumbersome. You can use HTML to construct the UI tree more easily.

Here's how to create a view tree from HTML:

  • ui.html

    <html>
      <head>
        <style>
          container {
            align-items: center;
            justify-content: center;
          }
          .sprite {
            width: 64px;
            height: 64px;
          }
        </style>
      </head>
      <body>
        <container>
          <character class="sprite"></character>
        </container>
      </body>
    </html>
    
  • ui.go

    //go:embed assets/html/ui.html
    var html string
    
    view := furex.Parse(html, &furex.ParseOptions{
      Width: 480,
      Height: 600,
      Components: map[string]furex.Component{
        "character": &widgets.Sprite{SpriteID: "mario.png"},
      },
    })
    

    Note: The <body> tag itself won't be converted as a View, but its children will be converted to View instances.

This example is equivalent to the following Go code. By using HTML for styling the UI, the code becomes more maintainable.

view := (&furex.View{
  Width: 480,
  Height: 600,
  AlignItems: furex.AlignItemsCenter,
  Justify: furex.JustifyCenter,
}).AddChild(
  &furex.View{
    Width: 64,
    Height: 64,
    Handler: &widgets.Sprite{SpriteID: "mario.png"},
  },
)

For a more extensive example, check out the example here and the embedded HTML file.

CSS Properties

The following table lists the available CSS properties:

CSS Property Type Available Values
left int Any integer value
right int Any integer value
top int Any integer value
bottom int Any integer value
width int Any integer value or percentage
height int Any integer value or percentage
margin-left int Any integer value
margin-top int Any integer value
margin-right int Any integer value
margin-bottom int Any integer value
position Position static, absolute
flex-direction Direction row, column
flex-wrap FlexWrap no-wrap, wrap, wrap-reverse
justify-content Justify flex-start, flex-end, center, space-between, space-around
align-items AlignItem stretch, flex-start, flex-end, center
align-content AlignContent flex-start, flex-end, center, space-between, space-around, stretch
flex-grow float64 Any float64 value
flex-shrink float64 Any float64 value
display Display flex, none

HTML Attributes

The following table lists the available HTML attributes:

HTML Attribute Type Available Values
id string Any string value
hidden bool true, false

Component Types

There are three types of components you can create in Furex:

  • Handler Instance: A furex.Handler instance, such as Drawer or Updater.
  • Factory Function: A function that returns a furex.Handler instance. This is useful when you want to create separate handler instances for each HTML tag.
  • Function Component: A function that returns a *furex.View instance. This is an alternative way to create components that encapsulate their own behavior and styles.

Global Components

To register a custom component globally, use the furex.RegisterComponents function. For example:

  func init() {
  	furex.RegisterComponents(furex.ComponentsMap{
  		"button":  &widgets.Button{},
  		"sprite": &widgets.Sprite{},
  	})
  }

Debugging

You can enable Debug Mode by setting the variable below.

furex.Debug = true

Contributions

Contributions are welcome! If you find a bug or have an idea for a new feature, feel free to open an issue or submit a pull request.

Documentation

Overview

Referenced code: https://github.com/golang/exp/blob/master/shiny/widget/flex/flex.go

Index

Constants

This section is empty.

Variables

View Source
var (
	Debug = false
)

Functions

func Float

func Float(f float64) *float64

func Int

func Int(i int) *int

func RegisterComponents

func RegisterComponents(cs ComponentsMap)

Types

type AlignContent

type AlignContent uint8

AlignContent is the 'align-content' property. It aligns container lines when there is extra space on the cross-axis.

const (
	AlignContentStart AlignContent = iota
	AlignContentEnd
	AlignContentCenter
	AlignContentSpaceBetween
	AlignContentSpaceAround
	AlignContentStretch
)

func (AlignContent) String

func (f AlignContent) String() string

type AlignItem

type AlignItem uint8

AlignItem aligns items along the cross axis.

const (
	AlignItemStretch AlignItem = iota
	AlignItemStart
	AlignItemEnd
	AlignItemCenter
)

func (AlignItem) String

func (f AlignItem) String() string

type ButtonHandler

type ButtonHandler interface {
	// HandlePress handle the event when user just started pressing the button
	// The parameter (x, y) is the location relative to the window (0,0).
	// touchID is the unique ID of the touch.
	// If the button is pressed by a mouse, touchID is -1.
	HandlePress(x, y int, t ebiten.TouchID)

	// HandleRelease handle the event when user just released the button.
	// The parameter (x, y) is the location relative to the window (0,0).
	// The parameter isCancel is true when the touch/left click is released outside of the button.
	HandleRelease(x, y int, isCancel bool)
}

ButtonHandler represents a button component.

type Component

type Component interface{}

The Component can be either a handler instance (e.g., DrawHandler), a factory function func() furex.Handler, or a function component func() *furex.View. This allows flexibility in usage: If you want to reuse the same handler instance for multiple HTML tags, pass the instance; otherwise, pass the factory function to create separate handler instances for each tag.

type ComponentsMap

type ComponentsMap map[string]Component

ComponentsMap is a type alias for a dictionary that associates custom HTML tags with their respective components. This enables a convenient way to manage and reference components based on their corresponding HTML tags.

type Direction

type Direction uint8

Direction is the direction in which flex items are laid out

const (
	Row Direction = iota
	Column
)

func (Direction) String

func (d Direction) String() string

type Display

type Display uint8

Display is the 'display' property

const (
	DisplayFlex Display = iota
	DisplayNone
)

func (Display) String

func (d Display) String() string

type DrawHandler

type DrawHandler interface {
	// HandleDraw function draws the content of the component inside the frame.
	// The frame parameter represents the location (x,y) and size (width,height) relative to the window (0,0).
	HandleDraw(screen *ebiten.Image, frame geo.Rectangle)
}

DrawHandler represents a component that can be added to a container. Deprectead: use Drawer instead

type Drawer

type Drawer interface {
	// Draw function draws the content of the component inside the frame.
	Draw(screen *ebiten.Image, frame geo.Rectangle, v *View)
}

Drawer represents a component that can be added to a container.

type ErrorList

type ErrorList struct {
	// contains filtered or unexported fields
}

func (*ErrorList) Add

func (e *ErrorList) Add(err error)

func (*ErrorList) Error

func (e *ErrorList) Error() string

func (*ErrorList) HasErrors

func (e *ErrorList) HasErrors() bool

type FlexAlign

type FlexAlign int

FlexAlign represents align of flex children

const (
	FlexCenter FlexAlign = iota
	FlexStart
	FlexEnd
	FlexSpaceBetween
)

func (FlexAlign) String

func (f FlexAlign) String() string

type FlexWrap

type FlexWrap uint8

FlexWrap controls whether the container is single- or multi-line, and the direction in which the lines are laid out.

const (
	NoWrap FlexWrap = iota
	Wrap
	WrapReverse
)

func (FlexWrap) String

func (f FlexWrap) String() string

type Handler

type Handler interface{}

Handler represents a component that can be added to a container.

func NewHandler

func NewHandler(opts HandlerOpts) Handler

NewHandler creates a new handler.

type HandlerOpts

type HandlerOpts struct {
	Update        func(v *View)
	Draw          func(screen *ebiten.Image, frame geo.Rectangle, v *View)
	HandlePress   func(x, y int, t ebiten.TouchID)
	HandleRelease func(x, y int, isCancel bool)
}

HandlerOpts represents the options for a handler.

type Justify

type Justify uint8

Justify aligns items along the main axis.

const (
	JustifyStart        Justify = iota // pack to start of line
	JustifyEnd                         // pack to end of line
	JustifyCenter                      // pack to center of line
	JustifySpaceBetween                // even spacing
	JustifySpaceAround                 // even spacing, half-size on each end
)

func (Justify) String

func (f Justify) String() string

type MouseEnterLeaveHandler

type MouseEnterLeaveHandler interface {
	// HandleMouseEnter handles the mouse enter.
	HandleMouseEnter(x, y int) bool
	// HandleMouseLeave handles the mouse leave.
	HandleMouseLeave()
}

MouseEnterLeaveHandler represets a component that handle mouse enter.

type MouseHandler

type MouseHandler interface {
	// HandleMouse handles the much move and returns true if it handles the mouse move.
	// The parameter (x, y) is the location relative to the window (0,0).
	HandleMouse(x, y int) bool
}

MouseHandler represents a component that handle mouse move.

type MouseLeftButtonHandler

type MouseLeftButtonHandler interface {
	// HandleJustPressedMouseButtonLeft handle left mouse button click just pressed.
	// The parameter (x, y) is the location relative to the window (0,0).
	// It returns true if it handles the mouse move.
	HandleJustPressedMouseButtonLeft(x, y int) bool
	// HandleJustReleasedMouseButtonLeft handles the touchID just released.
	// The parameter (x, y) is the location relative to the window (0,0).
	HandleJustReleasedMouseButtonLeft(x, y int)
}

MouseLeftButtonHandler represents a component that handle mouse button left click.

type NotButton

type NotButton interface {
	// IsButton returns true if the handler is a button.
	IsButton() bool
}

NotButton represents a component that is not a button. TODO: update HandlePress to return bool in the next major version.

type ParseOptions

type ParseOptions struct {
	// Components is a map of component name and handler.
	// For example, if you have a component named "start-button", you can define a handler
	// for it like this:
	// 	opts.Components := map[string]Handler{
	// 		"start-button": <your handler>,
	//  }
	// The component name must be in kebab-case.
	// You can use the component in your HTML like this:
	// 	<start-button></start-button>
	// Note: self closing tag is not supported.
	Components ComponentsMap
	// Width and Height is the size of the root view.
	// This is useful when you want to specify the width and height
	// outside of the HTML.
	Width  float64
	Height float64

	// Handler is the handler for the root view.
	Handler Handler
}

ParseOptions represents options for parsing HTML.

type Position

type Position uint8

Position is the 'position' property

const (
	PositionStatic Position = iota
	PositionAbsolute
)

func (Position) String

func (p Position) String() string

type SwipeDirection

type SwipeDirection int

SwipeDirection represents different swipe directions.

const (
	SwipeDirectionLeft SwipeDirection = iota
	SwipeDirectionRight
	SwipeDirectionUp
	SwipeDirectionDown
)

type SwipeHandler

type SwipeHandler interface {
	// HandleSwipe handles swipes.
	HandleSwipe(dir SwipeDirection)
}

SwipeHandler represents a component that handle swipe.

type TouchHandler

type TouchHandler interface {
	// HandleJustPressedTouchID handles the touchID just pressed and returns true if it handles the TouchID
	HandleJustPressedTouchID(touch ebiten.TouchID, x, y int) bool
	// HandleJustReleasedTouchID handles the touchID just released
	// Should be called only when it handled the TouchID when pressed
	HandleJustReleasedTouchID(touch ebiten.TouchID, x, y int)
}

TouchHandler represents a component that handle touches.

type UpdateHandler

type UpdateHandler interface {
	// Updater updates the state of the component by one tick.
	HandleUpdate()
}

UpdateHandler represents a component that updates by one tick. Deprectead: use Updater instead

type Updater

type Updater interface {
	// Update updates the state of the component by one tick.
	Update(frame geo.Rectangle, v *View)
}

Updater represents a component that updates by one tick.

type View

type View struct {
	// TODO: Remove these fields in the future.
	Left         float64
	Right        *float64
	Top          float64
	Bottom       *float64
	Width        float64
	WidthInPct   float64
	Height       float64
	HeightInPct  float64
	MarginLeft   float64
	MarginTop    float64
	MarginRight  float64
	MarginBottom float64
	Position     Position
	Direction    Direction
	Wrap         FlexWrap
	Justify      Justify
	AlignItems   AlignItem
	AlignContent AlignContent
	Grow         float64
	Shrink       float64
	Display      Display

	ID      string
	Raw     string
	TagName string
	Text    string
	Attrs   map[string]string
	Hidden  bool

	Handler Handler
	// contains filtered or unexported fields
}

View represents a UI element. You can set flex options, size, position and so on. Handlers can be set to create custom component such as button or list.

func Parse

func Parse(input string, opts *ParseOptions) *View

func (*View) AddChild

func (v *View) AddChild(views ...*View) *View

AddChild adds one or multiple child views

func (*View) AddTo

func (v *View) AddTo(parent *View) *View

AddTo add itself to a parent view

func (*View) Config

func (v *View) Config() ViewConfig

func (*View) Draw

func (v *View) Draw(screen *ebiten.Image)

Draw draws the view

func (*View) GetByID

func (v *View) GetByID(id string) (*View, bool)

GetByID returns the view with the specified id. It returns nil if not found.

func (*View) HandleJustPressedTouchID

func (ct *View) HandleJustPressedTouchID(touchID ebiten.TouchID, x, y int) bool

func (*View) HandleJustReleasedTouchID

func (ct *View) HandleJustReleasedTouchID(touchID ebiten.TouchID, x, y int)

func (*View) Layout

func (v *View) Layout()

Layout marks the view as dirty

func (*View) MustGetByID

func (v *View) MustGetByID(id string) *View

MustGetByID returns the view with the specified id. It panics if not found.

func (*View) PopChild

func (v *View) PopChild() *View

PopChild remove the last child view add to this view

func (*View) RemoveAll

func (v *View) RemoveAll()

RemoveAll removes all children view

func (*View) RemoveChild

func (v *View) RemoveChild(cv *View) bool

RemoveChild removes a specified view

func (*View) SetAlignContent

func (v *View) SetAlignContent(alignContent AlignContent)

SetAlignContent sets the align content property of the view.

func (*View) SetAlignItems

func (v *View) SetAlignItems(alignItems AlignItem)

SetAlignItems sets the align items property of the view.

func (*View) SetBottom

func (v *View) SetBottom(bottom float64)

SetBottom sets the bottom position of the view.

func (*View) SetDirection

func (v *View) SetDirection(direction Direction)

SetDirection sets the direction of the view.

func (*View) SetDisplay

func (v *View) SetDisplay(display Display)

SetDisplay sets the display property of the view.

func (*View) SetGrow

func (v *View) SetGrow(grow float64)

SetGrow sets the grow property of the view.

func (*View) SetHeight

func (v *View) SetHeight(height float64)

SetHeight sets the height of the view.

func (*View) SetHidden

func (v *View) SetHidden(hidden bool)

SetHidden sets the hidden property of the view.

func (*View) SetJustify

func (v *View) SetJustify(justify Justify)

SetJustify sets the justify property of the view.

func (*View) SetLeft

func (v *View) SetLeft(left float64)

SetLeft sets the left position of the view.

func (*View) SetMarginBottom

func (v *View) SetMarginBottom(marginBottom float64)

SetMarginBottom sets the bottom margin of the view.

func (*View) SetMarginLeft

func (v *View) SetMarginLeft(marginLeft float64)

SetMarginLeft sets the left margin of the view.

func (*View) SetMarginRight

func (v *View) SetMarginRight(marginRight float64)

SetMarginRight sets the right margin of the view.

func (*View) SetMarginTop

func (v *View) SetMarginTop(marginTop float64)

SetMarginTop sets the top margin of the view.

func (*View) SetPosition

func (v *View) SetPosition(position Position)

SetPosition sets the position of the view.

func (*View) SetRight

func (v *View) SetRight(right float64)

SetRight sets the right position of the view.

func (*View) SetShrink

func (v *View) SetShrink(shrink float64)

SetShrink sets the shrink property of the view.

func (*View) SetTop

func (v *View) SetTop(top float64)

SetTop sets the top position of the view.

func (*View) SetWidth

func (v *View) SetWidth(width float64)

SetWidth sets the width of the view.

func (*View) SetWrap

func (v *View) SetWrap(wrap FlexWrap)

SetWrap sets the wrap property of the view.

func (*View) Update

func (v *View) Update()

Update updates the view

func (*View) UpdateWithSize

func (v *View) UpdateWithSize(width, height float64)

UpdateWithSize the view with modified height and width

type ViewConfig

type ViewConfig struct {
	TagName      string
	ID           string
	Left         float64
	Right        *float64
	Top          float64
	Bottom       *float64
	Width        float64
	Height       float64
	MarginLeft   float64
	MarginTop    float64
	MarginRight  float64
	MarginBottom float64
	Position     Position
	Direction    Direction
	Wrap         FlexWrap
	Justify      Justify
	AlignItems   AlignItem
	AlignContent AlignContent
	Grow         float64
	Shrink       float64
	// contains filtered or unexported fields
}

This is for debugging and testing.

func (ViewConfig) Tree

func (cfg ViewConfig) Tree() string

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL