furex

package module
v2.4.5 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2024 License: BSD-3-Clause Imports: 16 Imported by: 11

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/yohamta/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 image.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 Int added in v2.3.6

func Int(i int) *int

func RegisterComponents added in v2.3.6

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 added in v2.3.4

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 added in v2.3.4

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 added in v2.3.0

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 added in v2.3.0

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 added in v2.3.4

func (d Direction) String() string

type Display added in v2.3.3

type Display uint8

Display is the 'display' property

const (
	DisplayFlex Display = iota
	DisplayNone
)

func (Display) String added in v2.3.4

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 image.Rectangle)
}

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

type Drawer added in v2.4.0

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

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

type ErrorList added in v2.3.0

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

func (*ErrorList) Add added in v2.3.0

func (e *ErrorList) Add(err error)

func (*ErrorList) Error added in v2.3.0

func (e *ErrorList) Error() string

func (*ErrorList) HasErrors added in v2.3.0

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 added in v2.3.4

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 added in v2.3.4

func (f FlexWrap) String() string

type Handler added in v2.3.0

type Handler interface{}

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

func NewHandler added in v2.4.2

func NewHandler(opts HandlerOpts) Handler

NewHandler creates a new handler.

type HandlerOpts added in v2.4.2

type HandlerOpts struct {
	Update        func(v *View)
	Draw          func(screen *ebiten.Image, frame image.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 added in v2.3.4

func (f Justify) String() string

type MouseEnterLeaveHandler added in v2.2.0

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

MouseEnterHandler represets a component that handle mouse enter.

type MouseHandler

type MouseHandler interface {
	// HandleMouse handles the mouch move and returns true if it handle 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
	// HandleJustReleasedTouchID 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 added in v2.2.3

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 added in v2.3.0

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  int
	Height int

	// 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 added in v2.3.4

func (p Position) String() string

type SwipeDirection added in v2.1.4

type SwipeDirection int

SwipeHandler represents different swipe directions.

const (
	SwipeDirectionLeft SwipeDirection = iota
	SwipeDirectionRight
	SwipeDirectionUp
	SwipeDirectionDown
)

type SwipeHandler added in v2.1.4

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 added in v2.4.0

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

Updater represents a component that updates by one tick.

type View

type View struct {
	// TODO: Remove these fields in the future.
	Left         int
	Right        *int
	Top          int
	Bottom       *int
	Width        int
	WidthInPct   float64
	Height       int
	HeightInPct  float64
	MarginLeft   int
	MarginTop    int
	MarginRight  int
	MarginBottom int
	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 added in v2.3.0

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 added in v2.1.2

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

AddTo add itself to a parent view

func (*View) Config added in v2.3.0

func (v *View) Config() ViewConfig

func (*View) Draw

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

Draw draws the view

func (*View) GetByID added in v2.3.3

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 added in v2.3.3

func (v *View) Layout()

Layout marks the view as dirty

func (*View) MustGetByID added in v2.3.3

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 added in v2.1.1

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 added in v2.3.4

func (v *View) SetAlignContent(alignContent AlignContent)

SetAlignContent sets the align content property of the view.

func (*View) SetAlignItems added in v2.3.4

func (v *View) SetAlignItems(alignItems AlignItem)

SetAlignItems sets the align items property of the view.

func (*View) SetBottom added in v2.3.6

func (v *View) SetBottom(bottom int)

SetBottom sets the bottom position of the view.

func (*View) SetDirection added in v2.3.4

func (v *View) SetDirection(direction Direction)

SetDirection sets the direction of the view.

func (*View) SetDisplay added in v2.3.4

func (v *View) SetDisplay(display Display)

SetDisplay sets the display property of the view.

func (*View) SetGrow added in v2.3.4

func (v *View) SetGrow(grow float64)

SetGrow sets the grow property of the view.

func (*View) SetHeight added in v2.3.4

func (v *View) SetHeight(height int)

SetHeight sets the height of the view.

func (*View) SetHidden added in v2.3.4

func (v *View) SetHidden(hidden bool)

SetHidden sets the hidden property of the view.

func (*View) SetJustify added in v2.3.4

func (v *View) SetJustify(justify Justify)

SetJustify sets the justify property of the view.

func (*View) SetLeft added in v2.3.4

func (v *View) SetLeft(left int)

SetLeft sets the left position of the view.

func (*View) SetMarginBottom added in v2.3.4

func (v *View) SetMarginBottom(marginBottom int)

SetMarginBottom sets the bottom margin of the view.

func (*View) SetMarginLeft added in v2.3.4

func (v *View) SetMarginLeft(marginLeft int)

SetMarginLeft sets the left margin of the view.

func (*View) SetMarginRight added in v2.3.4

func (v *View) SetMarginRight(marginRight int)

SetMarginRight sets the right margin of the view.

func (*View) SetMarginTop added in v2.3.4

func (v *View) SetMarginTop(marginTop int)

SetMarginTop sets the top margin of the view.

func (*View) SetPosition added in v2.3.4

func (v *View) SetPosition(position Position)

SetPosition sets the position of the view.

func (*View) SetRight added in v2.3.6

func (v *View) SetRight(right int)

SetRight sets the right position of the view.

func (*View) SetShrink added in v2.3.4

func (v *View) SetShrink(shrink float64)

SetShrink sets the shrink property of the view.

func (*View) SetTop added in v2.3.4

func (v *View) SetTop(top int)

SetTop sets the top position of the view.

func (*View) SetWidth added in v2.3.4

func (v *View) SetWidth(width int)

SetWidth sets the width of the view.

func (*View) SetWrap added in v2.3.4

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 added in v2.1.6

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

UpdateWithSize the view with modified height and width

type ViewConfig added in v2.3.0

type ViewConfig struct {
	TagName      string
	ID           string
	Left         int
	Right        *int
	Top          int
	Bottom       *int
	Width        int
	Height       int
	MarginLeft   int
	MarginTop    int
	MarginRight  int
	MarginBottom int
	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 added in v2.3.4

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