RuleDiamond draws the rule-diamond-rule of Heading, Dialog, Result, Card titles and the ornament Divider; Frame draws the inset line with corner diamonds of Card and decorated Dialog. Both snap the diamonds, and the rule's diamond sits on the pixel-aligned line. Snapshots: card (all densities, light and dark) moves the title rule's diamond onto the rule's pixel row, an 8x8 region. Decorated dialog (both densities, light and dark) loses the surface-colored halo under each corner diamond, so the frame line now runs into the diamond as on Card and the panel border is no longer notched at the corners. Heading, Divider and Result render unchanged.
130 lines
3.8 KiB
Go
130 lines
3.8 KiB
Go
package mujica
|
|
|
|
import (
|
|
"log/slog"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"mujicaui/icons"
|
|
"mujicaui/internal"
|
|
"mujicaui/theme"
|
|
)
|
|
|
|
// DialogOptions configure a Dialog. Title is required.
|
|
type DialogOptions struct {
|
|
Title string
|
|
Description string
|
|
// Width is 440 DIPs when zero; the window always bounds it.
|
|
Width float32
|
|
// Decorated draws the double border, corner ornaments and title rule.
|
|
Decorated bool
|
|
// Actions builds the footer, usually buttons, aligned to the end.
|
|
Actions func()
|
|
// KeepOnBackdrop ignores clicks on the backdrop.
|
|
KeepOnBackdrop bool
|
|
// KeepOnEscape ignores Escape.
|
|
KeepOnEscape bool
|
|
// HideClose removes the close button from the header.
|
|
HideClose bool
|
|
}
|
|
|
|
// Dialog shows a modal dialog while *open is true, trapping and then restoring the focus.
|
|
func Dialog(c *ui.Context, open *bool, opts DialogOptions, content func()) *ui.Element {
|
|
if opts.Title == "" {
|
|
panic("mujica: Dialog needs a Title")
|
|
}
|
|
site, focus := overlaySite(c)
|
|
if !*open {
|
|
focus.closed(c, site)
|
|
return nil
|
|
}
|
|
overlayLayer(c, open)
|
|
k := Tokens(c)
|
|
shown, backClicked, closed := true, false, false
|
|
panel := ui.DialogBase(c, &shown, func(back, panel *ui.Element) {
|
|
backClicked = back.Clicked()
|
|
p := overlayEnter(c, panel)
|
|
back.Background(overlayScrim(k).Alpha(p)).Padding(16)
|
|
dialogPanel(c, panel, opts.Width, opts.Decorated, p)
|
|
panel.Label(opts.Title)
|
|
if opts.Description != "" {
|
|
panel.Description(opts.Description)
|
|
}
|
|
closed = dialogHeader(c, opts.Title, opts.Decorated, !opts.HideClose, "dialog.close")
|
|
// Description and content scroll together so the actions always keep their room.
|
|
ui.Scroll(c).Shrink(1).Gap(Density(c).Unit() * 3).Children(func() {
|
|
if opts.Description != "" {
|
|
ui.Text(c, opts.Description).TextColor(k.TextMuted)
|
|
}
|
|
if content != nil {
|
|
content()
|
|
}
|
|
})
|
|
if opts.Actions != nil {
|
|
dialogActions(c, opts.Actions)
|
|
}
|
|
})
|
|
focus.shown(panel)
|
|
switch {
|
|
case closed:
|
|
slog.Debug("mujica: dialog close", "by", "button")
|
|
*open = false
|
|
case !shown && backClicked && !opts.KeepOnBackdrop:
|
|
slog.Debug("mujica: dialog close", "by", "backdrop")
|
|
*open = false
|
|
case !shown && !backClicked && !opts.KeepOnEscape:
|
|
slog.Debug("mujica: dialog close", "by", "escape")
|
|
*open = false
|
|
}
|
|
return panel
|
|
}
|
|
|
|
// dialogPanel styles a centered dialog panel at entrance progress p.
|
|
func dialogPanel(c *ui.Context, panel *ui.Element, width float32, decorated bool, p float32) {
|
|
k := Tokens(c)
|
|
u := Density(c).Unit()
|
|
if width == 0 {
|
|
width = 440
|
|
}
|
|
w, h := c.Size()
|
|
overlayStyle(c, panel, p)
|
|
panel.Width(width).MaxWidth(w-32).MaxHeight(h-32).Padding(u*6).Gap(u*3).Margin(8*(1-p), 0, 0, 0)
|
|
if decorated {
|
|
panel.Border(theme.BorderWidth, k.Ornament).Padding(u*6 + 6).DrawOver(func(pt *ui.Painter, r ui.Rect) {
|
|
internal.Frame(pt, r, 5, 7, k.Ornament)
|
|
})
|
|
}
|
|
}
|
|
|
|
// dialogHeader builds the title and close button; it reports a close click.
|
|
func dialogHeader(c *ui.Context, title string, decorated, closable bool, closeKey string) bool {
|
|
k := Tokens(c)
|
|
u := Density(c).Unit()
|
|
clicked := false
|
|
ui.Column(c).Gap(u).Shrink(0).Children(func() {
|
|
ui.Row(c).Gap(u * 3).AlignItems(ui.Start).Children(func() {
|
|
ui.Text(c, title).Font(theme.SerifFont).FontSize(FontSize(c, theme.H3Size)).FontWeight(600).Grow(1)
|
|
if closable {
|
|
clicked = iconAction(c, icons.Must("x"), msg(c, closeKey)).Margin(-4, -4, 0, 0).Clicked()
|
|
}
|
|
})
|
|
if decorated {
|
|
dialogRule(c, k.Ornament)
|
|
}
|
|
})
|
|
return clicked
|
|
}
|
|
|
|
// dialogRule is the ornament line under a decorated title.
|
|
func dialogRule(c *ui.Context, ink ui.Color) {
|
|
ui.Box(c).FillWidth().Height(9).Margin(2, 0, 2, 0).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
internal.RuleDiamond(p, r, 10, 7, ink)
|
|
})
|
|
}
|
|
|
|
// dialogActions lays out a dialog footer.
|
|
func dialogActions(c *ui.Context, actions func()) {
|
|
u := Density(c).Unit()
|
|
ui.Row(c).Gap(u*2).Justify(ui.End).Wrap().Shrink(0).Margin(u*2, 0, 0, 0).Children(actions)
|
|
}
|