Some semantic information is automatically extracted, but some must be
provided by UI components. This change enriches the generic and material
widgets with such information.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
To make the semantic relation between the clickable area and its
content clear, it will be important for the clickable clip operation
to cover all of the clickable content.
API change: users of widget.Clickable must now pass the clickable
content to Layout.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The op.Save and Load methods exist to support the need for
transformation, clip, pointer area state to behave as stacks. For
example, layout needs to apply an offset to its children but not
subsequent operations.
Before this change, op.Save and Load were used to save and restore the
state:
ops := new(op.Ops)
// Save state.
state := op.Save(ops)
// Apply offset.
op.Offset(...).Add(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
state.Load()
A drawback with the op.Save mechanism is that there is no direct
connection between the state change and the saving and loading of state.
This causes confusion as to when a Save/Load is needed and who is
responsible for performing them, which leads to subtle bugs and over-use
of Save/Loads.
This change gets rid of the general state stack and replaces it with
per-state stacks. There is now a stack for transformation, clip, pointer
areas, and they can only be restored by the code pushing state to them.
The example above now becomes:
ops := new(op.Ops)
// Push offset to the transformation stack.
stack := op.Offset(...).Push(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
stack.Pop()
For convenience, transformation also be Add'ed if the stack operation is
not required.
Simple state such as the current material no longer has a way to be
restored; it is assumed the client of a PaintOp adds their desired
material operation before it.
API change: replace op.Save/Load with explicit Push/Pop scopes for
op.TransformOps, pointer.AreaOps, clip.Ops.
To ease porting, this change retains a version of op.Save/Load that
saves and restores the transformation and clip stacks. It also retains
an Add method for clip.Op.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This introduces a new material.Palette type that captures the color information
necessary to render a widget. This type is embedded in the material.Theme to
make it easier to swap to a different palette for part of the UI by reassinging
the Palette field.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Before this change, events were typically processed twice or more per
widget: once in the Layout method for refreshing the visual state, and
once per method that queries for state changes.
One example is widget.Clickable that processed events in both its Layout
and Clicked method.
This change establishes the convention that events are processed once, in
the Layout method. There are several advantages to that approach:
- Query methods such as Clickable.Clicked no longer need a layout.Context.
- State updates from events only occur in Layout.
- Widgets are simplified because they won't need a separate processEvents
(or similar) method and won't forget to call it from methods other than Layout.
- Useless calls to gtx.Events are avoided (gtx.Events only returns events
for the first call each frame for a given event.Tag).
The disadvantage is that state updates from input events will not appear
before Layout. For example, in the call sequence
var btn *widget.Clickable
if btn.Clicked() {...}
btn.Layout(...)
the Clicked call will not detect an incoming click until the frame after it
happened.
This is ok because
- The Gio event router automatically dispatches an extra frame after events
arrive, bounding the latency from events to queries such as Clicked to
at most one frame (~17 ms).
- The potential extra frame of latency does not apply to Layout methods as long
as they process events before drawing. In other words, the visual feedback
from input events are not delayed because of this change.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Instead of, say,
var th *material.Theme
var btn *widget.Clickable
material.Button(th, "Click me").Layout(gtx, btn)
move the widget state objects to the constructor:
material.Button(th, btn, "Click me").Layout(gtx)
The advatage is that several widgets can now be used without
wrapping them in function literals. For example,
layout.Inset{}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
material.Button(th, "Click me").Layout(gtx, btn)
})
collapses to just
layout.Inset{}.Layout(gtx, material.Button(th, btn, "Click me").Layout)
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Change the definition of Widget from the implicit
type Widget func()
to the explicit functional
type Widget func(gtx layout.Context) layout.Dimensions
The advantages are numerous:
- Clearer connection between the incoming context and the output dimensions.
- Returning the Dimensions are impossible to omit.
- Contexts passed by value, so its fields can be exported
and freely mutated by the program.
The only disadvantage is the longer function literals and the many "returns".
What tipped the scales in favour of the explicit Widget variant is that type
aliases can dramatically shorten the literals:
type (
C = layout.Context
D = layout.Dimensions
)
widget := func(gtx C) D {
...
}
Note that the aliases are not part of the Gio API and it is up to each user
whether they want to use them.
Finally the Go proposal for lightweight function literals,
https://github.com/golang/go/issues/21498, may remove the disadvantage
completely in future.
Context becomes a plain struct with only public fields, and its Reset is
replaced by a NewContext convenience constructor.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
We're about to introduce the Switch widget that re-uses the same
state type as CheckBox. The Bool name covers both uses.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Similar to the previous change to Enum, expose the current state of
the CheckBox. Rename the Checked method to just Update and get rid
of the SetChecked method.
Fixes gio#100
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The multitude of widget methods on Theme is unnecessary coupling in that all
possible widgets either have to be included in package material, or be
different than 3rd party widgets:
var th *Theme
// Core widget, calling a method on Theme.
th.Button(...).Layout(...)
// 3rd party widget, calling a function taking a Theme.
datepicker.New(th, ...).Layout(...)
Another reason for the Theme methods was to enable a poor man's
theme replacement, so that you could use the same code for
compatible themes. For example,
mat.Button(...).Layout(...)
would not need to change if the type of mat changed, as long as
the new type had a compatible method Button.
However, that point misses the fact that the mat variable had to
be declared somewhere, naming the theme package:
var mat *material.Theme (or, say, *cocoa.Theme)
A better and complete way to replace a theme is to use import renaming.
For example, to replace the material theme with a hypothetical Windows
theme, replace
import theme "gioui.org/widget/material"
with
import theme "github.com/somebody/windows
This change moves all Theme widget methods to be standalone functions,
and renames the widget style types accordingly.
For example, instead of the method
func (t *Theme) Button(...) Button
there is now a function
func Button(t *Theme, ...) ButtonStyle
Signed-off-by: Elias Naur <mail@eliasnaur.com>
First, replace LayoutOptions with an explicit maximum width parameter. The
single-field option struct doesn't carry its weight, and I don't think we'll
see more global layout options in the future. Rather, I expect options to cover
spans of text or be part of a Font.
Second, replace the unit.Converter with an scaled text size. It's simpler and
allow the Editor and similar widgets to easily detect whether their cached
layouts are stale. Package text no longer depends on package unit, which is
now dealt with at the widget-level only.
Finally, remove the Size field from Font. It was a design mistake: a Font is
assumed to cover all sizes, as evidenced by the FontRegistry disregarding
Size when looking up fonts.
Signed-off-by: Elias Naur <mail@eliasnaur.com>