~pierrec/giox

5b5215e4e26a378965979dab8f156490e183af80 — pierre 9 months ago 325c427
added PopupList
made Hover a type

Signed-off-by: pierre <pierre.curto@gmail.com>
M cmd/iconx/main.go => cmd/iconx/main.go +2 -2
@@ 89,7 89,7 @@ func loop(w *app.Window) error {
}

type uiMain struct {
	Hover color.NRGBA
	Hover widgetx.Hover

	th      *material.Theme
	tree    widgetx.Tree


@@ 157,7 157,7 @@ func (ui *uiMain) init() {
			Children: iconsTree.Children,
			Axis:     layout.Vertical,
		}
		ui.Hover = color.NRGBA(colornames.Lightgrey)
		ui.Hover = widgetx.Hover{Color: color.NRGBA(colornames.Lightgrey)}
		ui.menu = widgetx.Icons{
			Hover: ui.Hover,
			Size:  ui.th.TextSize,

M widgetx/click.go => widgetx/click.go +108 -15
@@ 4,6 4,8 @@ import (
	"image"
	"image/color"

	"gioui.org/f32"
	"gioui.org/io/key"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"


@@ 15,18 17,23 @@ import (
	"git.sr.ht/~pierrec/giox/layoutx"
)

// Hover returns a widget which fills its min area with c when hovered.
// Hover fills its min area with Color when hovered.
//
// Typical use is with layout.Expanded.
func Hover(c color.NRGBA, hovered bool) layout.Widget {
	return func(gtx layout.Context) layout.Dimensions {
		if hovered {
			defer op.Save(gtx.Ops).Load()
			clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
			paint.Fill(gtx.Ops, c)
		}
		return layout.Dimensions{Size: gtx.Constraints.Min}
type Hover struct {
	Color  color.NRGBA
	Radius unit.Value
}

func (h Hover) Layout(gtx layout.Context, hovered bool) layout.Dimensions {
	if hovered {
		defer op.Save(gtx.Ops).Load()
		r := image.Rectangle{Max: gtx.Constraints.Min}
		radius := gtx.Metric.Px(h.Radius)
		clip.UniformRRect(layout.FRect(r), float32(radius)).Add(gtx.Ops)
		paint.Fill(gtx.Ops, h.Color)
	}
	return layout.Dimensions{Size: gtx.Constraints.Min}
}

// ClickElement displays the ith element.


@@ 34,7 41,7 @@ type ClickElement func(layout.Context, int, *widget.Clickable) layout.Dimensions

// ClickList manages a list of clickable items.
type ClickList struct {
	Hover   color.NRGBA
	Hover   Hover
	List    layout.List
	clicks  []widget.Clickable
	clicked int


@@ 42,7 49,7 @@ type ClickList struct {
}

type ClickListWrap struct {
	Hover     color.NRGBA
	Hover     Hover
	Separator color.NRGBA
	List      layoutx.ListWrap
	clicks    []widget.Clickable


@@ 52,12 59,89 @@ type ClickListWrap struct {

// ClickIcon provides a clickable icon with hover shadow.
type ClickIcon struct {
	Hover color.NRGBA
	Hover Hover
	Size  unit.Value
	Icon  widget.Icon
	click *widget.Bool
}

// PopupList provides a popup list.
type PopupList struct {
	// Wrap wraps the whole list.
	Wrap       func(layout.Context, layout.Widget) layout.Dimensions
	List       ClickList
	Background color.NRGBA
	Shadow     color.NRGBA
	Width      unit.Value
	Height     unit.Value
	click      widget.Clickable
	closed     bool
}

func (pl *PopupList) update(gtx layout.Context) {
	if pl.click.Clicked() {
		pl.closed = true
		op.InvalidateOp{}.Add(gtx.Ops)
		return
	}
	for _, ev := range gtx.Events(pl) {
		if e, ok := ev.(key.Event); ok && e.Name == key.NameEscape {
			pl.closed = true
			return
		}
	}
}

func (pl *PopupList) Layout(gtx layout.Context, pos f32.Point, num int, el layout.ListElement) layout.Dimensions {
	pl.update(gtx)
	macro := op.Record(gtx.Ops)
	paint.FillShape(gtx.Ops, pl.Shadow, clip.Rect{Max: gtx.Constraints.Max}.Op())

	key.InputOp{Tag: pl}.Add(gtx.Ops)
	key.FocusOp{Tag: pl}.Add(gtx.Ops)
	cgtx := gtx
	cgtx.Constraints.Min = gtx.Constraints.Max
	pl.click.Layout(cgtx)

	op.Offset(pos).Add(gtx.Ops)
	gtx.Constraints.Min = image.Point{}
	dims := layout.Stack{}.Layout(gtx,
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			size := image.Pt(
				gtx.Metric.Px(pl.Width),
				gtx.Metric.Px(pl.Height),
			)
			paint.FillShape(gtx.Ops, pl.Background, clip.Rect{Max: size}.Op())
			return layout.Dimensions{Size: size}
		}),
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			gtx.Constraints.Max = gtx.Constraints.Min
			wrap := popupWrap
			if pl.Wrap != nil {
				wrap = pl.Wrap
			}
			return wrap(gtx, func(gtx layout.Context) layout.Dimensions {
				return pl.List.Layout(gtx, num, func(gtx layout.Context, idx int, click *widget.Clickable) layout.Dimensions {
					return el(gtx, idx)
				})
			})
		}),
	)
	op.Defer(gtx.Ops, macro.Stop())
	return dims
}

// Closed reports whether or not the popup was closed (single click outside of the popup or ESC key pressed).
func (pl *PopupList) Closed() bool {
	closed := pl.closed
	pl.closed = false
	return closed
}

func popupWrap(gtx layout.Context, w layout.Widget) layout.Dimensions {
	return w(gtx)
}

func (cl *ClickIcon) Bind(bar *Icons, idx int) {
	bar.init()
	cl.click = &bar.status[idx]


@@ 76,7 160,9 @@ func (cl *ClickIcon) init() {
func (cl *ClickIcon) Layout(gtx layout.Context) layout.Dimensions {
	cl.init()
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(Hover(cl.Hover, cl.click.Hovered())),
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			return cl.Hover.Layout(gtx, cl.click.Hovered())
		}),
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			return cl.Icon.Layout(gtx, cl.Size)
		}),


@@ 119,7 205,9 @@ func (cl *ClickList) Layout(gtx layout.Context, n int, el ClickElement) layout.D
	return cl.List.Layout(gtx, n, func(gtx layout.Context, idx int) layout.Dimensions {
		click := &cl.clicks[idx]
		return layout.Stack{}.Layout(gtx,
			layout.Expanded(Hover(cl.Hover, click.Hovered())),
			layout.Expanded(func(gtx layout.Context) layout.Dimensions {
				return cl.Hover.Layout(gtx, click.Hovered())
			}),
			layout.Stacked(func(gtx layout.Context) layout.Dimensions {
				return el(gtx, idx, click)
			}),


@@ 133,6 221,11 @@ func (cl *ClickList) Clicked() (pos int, click widget.Click) {
	return cl.clicked - 1, cl.click
}

func (cl *ClickList) Hovered(idx int) bool {
	cl.update()
	return cl.clicks[idx].Hovered()
}

func (cl *ClickListWrap) init(n int) {
	if cn := len(cl.clicks); cn < n {
		cl.clicks = append(cl.clicks, make([]widget.Clickable, n-cn)...)


@@ 164,7 257,7 @@ func (cl *ClickListWrap) Layout(gtx layout.Context, n int, el ClickElement) layo
				layout.Expanded(func(gtx layout.Context) layout.Dimensions {
					size := gtx.Constraints.Min
					gtx.Constraints.Min = cl.csMin(gtx.Constraints)
					Hover(cl.Hover, click.Hovered())(gtx)
					cl.Hover.Layout(gtx, click.Hovered())
					return layout.Dimensions{Size: size}
				}),
				layout.Stacked(func(gtx layout.Context) layout.Dimensions {

M widgetx/file.go => widgetx/file.go +34 -30
@@ 84,6 84,7 @@ type File struct {
		SortDesc        widget.Icon
		NewFolder       widget.Icon
	}
	Hover Hover
	// Path is the current path.
	Path string
	// Selection determines the number and kind of files or directories to be selected.


@@ 140,7 141,7 @@ type filePanel struct {
type clickColumns struct {
	Background color.NRGBA
	Color      color.NRGBA
	Hover      color.NRGBA
	Hover      Hover
	Active     bool
	Size       unit.Value
	columns    [6]clickColumn


@@ 320,7 321,7 @@ func (p *filePanel) Init(sz unit.Value, stop widget.Icon) {
	p.search = fileInput{
		Icon: LoadIcon(icons.ActionSearch),
		Stop: ClickIcon{
			Hover: colorx.Hover(p.Area.Palette.Bg),
			Hover: Hover{Color: colorx.Hover(p.Area.Palette.Bg)},
			Size:  sz,
			Icon:  stop,
		},


@@ 338,8 339,8 @@ func (p *filePanel) Init(sz unit.Value, stop widget.Icon) {
			},
		},
		ListCue: ListCue{
			Color1: colorx.MulAlpha(p.Bar.Hover, 16),
			Color2: colorx.MulAlpha(p.Bar.Hover, 80),
			Color1: colorx.MulAlpha(p.Bar.Hover.Color, 16),
			Color2: colorx.MulAlpha(p.Bar.Hover.Color, 80),
			Width:  unit.Dp(30),
		},
	}


@@ 734,7 735,7 @@ func (cc *clickColumn) Layout(cols *clickColumns, gtx layout.Context, label func
			if !cc.click.Hovered() {
				return cc.click.Layout(gtx)
			}
			return colorx.Fill(gtx, cols.Hover, cc.click.Layout)
			return colorx.Fill(gtx, cols.Hover.Color, cc.click.Layout)
		}),
	)
}


@@ 944,27 945,35 @@ func (sl *stringList) Clicked() (pos, num int) {
func (f *File) init() {
	if f.path == "" {
		f.data = fileData{
			Border:        f.Palettes.Main.ContrastBg,
			Background:    f.Palettes.Main.Bg,
			DimBackground: colorx.Hover(f.Palettes.Main.Bg),
			Border: f.Palettes.Main.ContrastBg,
			List: PopupList{
				List: ClickList{
					Hover: f.Hover,
					List:  layout.List{Axis: layout.Vertical},
				},
				Background: f.Palettes.Main.Bg,
				Shadow:     colorx.MulAlpha(f.Palettes.Main.Bg, 32),
				Width:      unit.Px(300),
				Height:     unit.Px(300),
			},
		}

		clickListWrap := ClickListWrap{
			Hover:     colorx.Hover(f.Palettes.Area.Bg),
			Hover:     f.Hover,
			Separator: colorx.Hover(f.Palettes.Area.ContrastBg),
			List: layoutx.ListWrap{
				Axis: layout.Vertical,
			},
		}
		clickList := ClickList{
			Hover: colorx.Hover(f.Palettes.Area.Bg),
			Hover: f.Hover,
			List: layout.List{
				Axis: layout.Horizontal,
			},
		}
		iconBar := func(icons ...widget.Icon) Icons {
			return Icons{
				Hover: colorx.Hover(f.Palettes.Area.Bg),
				Hover: f.Hover,
				Size:  f.Text.Label.Size,
				Icons: icons,
				List: layout.List{


@@ 1020,7 1029,7 @@ func (f *File) init() {
			Columns: clickColumns{
				Background: f.Palettes.Area.Bg,
				Color:      f.Palettes.Area.Fg,
				Hover:      colorx.Hover(f.Palettes.Area.Bg),
				Hover:      f.Hover,
				columns: [...]clickColumn{
					{Name: colName, Kind: colString, Resize: Resizable{Ratio: 0.3}},
					{Name: colLastChange, Kind: colTime, Resize: Resizable{Ratio: 0.3}, Human: true},


@@ 1032,7 1041,7 @@ func (f *File) init() {
			new: fileInput{
				Icon: LoadIcon(icons.FileCreateNewFolder),
				Stop: ClickIcon{
					Hover: colorx.Hover(f.Palettes.Area.Bg),
					Hover: f.Hover,
					Size:  f.Text.Label.Size,
					Icon:  f.Icons.Close,
				},


@@ 1056,7 1065,7 @@ func (f *File) init() {
			Columns: clickColumns{
				Background: f.Palettes.Area.Bg,
				Color:      f.Palettes.Area.Fg,
				Hover:      colorx.Hover(f.Palettes.Area.Bg),
				Hover:      f.Hover,
				columns: [...]clickColumn{
					{Name: colName, Kind: colFileName, Resize: Resizable{Ratio: 0.3}},
					{Name: colExt, Kind: colFileExt, Resize: Resizable{Ratio: 0.1}},


@@ 1274,25 1283,19 @@ func (f *File) layoutFavRec(gtx layout.Context) layout.Dimensions {
}

func (f *File) layoutPaths(gtx layout.Context) layout.Dimensions {
	var menuDims layout.Dimensions
	defer func() {
		offset := image.Pt(
			barPartitions*menuDims.Size.X/len(f.pBar.Icons),
			menuDims.Size.Y,
		)
		f.data.Layout(gtx, offset, f.body1)
	}()
	return layout.Flex{
		Axis:      layout.Horizontal,
		Alignment: layout.Middle,
	}.Layout(gtx,
		layout.Rigid(func(gtx layout.Context) (dims layout.Dimensions) {
			defer func() {
				if f.data.Active {
					m := op.Record(gtx.Ops)
					// Size of the popup.
					gtx.Constraints.Min = image.Pt(300, 300)
					// Move under the Partitions icon.
					//op.Offset(layout.FPt()).Add(gtx.Ops)
					offset := image.Pt(
						barPartitions*dims.Size.X/len(f.pBar.Icons), dims.Size.Y,
					)
					f.data.Layout(gtx, offset, f.body1)
					op.Defer(gtx.Ops, m.Stop())
				}
			}()
		layout.Rigid(func(gtx layout.Context) layout.Dimensions {
			for idx := range f.pBar.Icons {
				if !f.pBar.Clicked(idx) {
					continue


@@ 1315,7 1318,8 @@ func (f *File) layoutPaths(gtx layout.Context) layout.Dimensions {
					f.data.Active = !f.data.Active
				}
			}
			return f.pBar.Layout(gtx)
			menuDims = f.pBar.Layout(gtx)
			return menuDims
		}),
		layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
			return fileArea{

M widgetx/filedata.go => widgetx/filedata.go +27 -60
@@ 9,32 9,22 @@ import (
	"sort"
	"strings"

	"gioui.org/gesture"
	"gioui.org/io/key"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/paint"
	"gioui.org/unit"
	"gioui.org/widget"

	"github.com/shirou/gopsutil/disk"

	"git.sr.ht/~pierrec/giox/colorx"
)

type fileData struct {
	Active        bool
	Border        color.NRGBA
	Background    color.NRGBA
	DimBackground color.NRGBA
	Partitions    []disk.PartitionStat
	Current       *disk.PartitionStat
	Dirs          []os.FileInfo
	Files         []os.FileInfo
	list          ClickList
	click         gesture.Click
	changed       bool
	Active     bool
	Border     color.NRGBA
	Partitions []disk.PartitionStat
	Current    *disk.PartitionStat
	Dirs       []os.FileInfo
	Files      []os.FileInfo
	List       PopupList
	changed    bool
}

// Load loads data into fd.


@@ 104,30 94,25 @@ func (fd *fileData) Load(path string) (string, error) {
}

func (fd *fileData) init() {
	if fd.list.List.Axis == layout.Horizontal {
		fd.list.List.Axis = layout.Vertical
		fd.list.Hover = colorx.Hover(fd.Background)
	if fd.List.Wrap == nil {
		fd.List.Wrap = func(gtx layout.Context, w layout.Widget) layout.Dimensions {
			pad := unit.Dp(4)
			return widget.Border{
				Color: fd.Border,
				Width: pad.Scale(0.5),
			}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				return layout.UniformInset(pad).Layout(gtx, w)
			})
		}
	}
}

func (fd *fileData) update(gtx layout.Context) {
	for _, ev := range gtx.Queue.Events(fd) {
		switch ev := ev.(type) {
		case key.Event:
			// Close the popup is ESC is pressed.
			if ev.Name == key.NameEscape {
				fd.Active = false
				return
			}
		}
	}
	for _, ev := range fd.click.Events(gtx.Queue) {
		if ev.Type == gesture.TypeClick {
			fd.Active = false
		}
	if fd.List.Closed() {
		fd.Active = false
	}
	// Item in the popup selected.
	if i, _ := fd.list.Clicked(); i >= 0 {
	if i, _ := fd.List.List.Clicked(); i >= 0 {
		fd.Active = false
		fd.Current = &fd.Partitions[i]
		fd.changed = true


@@ 136,31 121,13 @@ func (fd *fileData) update(gtx layout.Context) {

func (fd *fileData) Layout(gtx layout.Context, offset image.Point, label func(layout.Context, string) layout.Dimensions) layout.Dimensions {
	fd.init()
	// Postpone the update to make sure events are processed
	// within the op.Defer call of fd.
	defer fd.update(gtx)
	if fd.Active {
		// Catch any key and mouse click.
		key.InputOp{Tag: fd}.Add(gtx.Ops)
		key.FocusOp{Tag: fd}.Add(gtx.Ops)
		paint.Fill(gtx.Ops, colorx.MulAlpha(fd.DimBackground, 32))
		pointer.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Add(gtx.Ops)
		fd.click.Add(gtx.Ops)
		op.Offset(layout.FPt(offset)).Add(gtx.Ops)
	fd.update(gtx)
	if !fd.Active {
		return layout.Dimensions{}
	}
	gtx.Constraints.Max = gtx.Constraints.Min
	return widget.Border{
		Color: fd.Border,
		Width: unit.Dp(2),
	}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return colorx.Fill(gtx, fd.Background, func(gtx layout.Context) layout.Dimensions {
			return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				return fd.list.Layout(gtx, len(fd.Partitions), func(gtx layout.Context, idx int, click *widget.Clickable) layout.Dimensions {
					gtx.Constraints.Min.X = gtx.Constraints.Max.X
					return label(gtx, fd.Partitions[idx].Mountpoint)
				})
			})
		})
	return fd.List.Layout(gtx, layout.FPt(offset), len(fd.Partitions), func(gtx layout.Context, idx int) layout.Dimensions {
		gtx.Constraints.Min.X = gtx.Constraints.Max.X
		return label(gtx, fd.Partitions[idx].Mountpoint)
	})
}


M widgetx/icons.go => widgetx/icons.go +9 -2
@@ 10,7 10,7 @@ import (

// Icons represents a toolbar made of icons.
type Icons struct {
	Hover  color.NRGBA
	Hover  Hover
	Size   unit.Value
	Icons  []widget.Icon
	status []widget.Bool


@@ 31,7 31,9 @@ func (s *Icons) Layout(gtx layout.Context) layout.Dimensions {
			return layout.Dimensions{}
		}
		return layout.Stack{}.Layout(gtx,
			layout.Expanded(Hover(s.Hover, s.status[idx].Hovered())),
			layout.Expanded(func(gtx layout.Context) layout.Dimensions {
				return s.Hover.Layout(gtx, s.status[idx].Hovered())
			}),
			layout.Stacked(func(gtx layout.Context) layout.Dimensions {
				return ic.Layout(gtx, s.Size)
			}),


@@ 50,6 52,11 @@ func (s *Icons) Clicked(idx int) bool {
	return s.status[idx].Changed()
}

func (s *Icons) Hovered(idx int) bool {
	s.init()
	return s.status[idx].Hovered()
}

func (s *Icons) Status(idx int) bool {
	s.init()
	return s.status[idx].Value

M widgetx/materialx/file.go => widgetx/materialx/file.go +4 -0
@@ 6,6 6,7 @@ import (

	"golang.org/x/exp/shiny/materialdesign/icons"

	"git.sr.ht/~pierrec/giox/colorx"
	"git.sr.ht/~pierrec/giox/widgetx"
)



@@ 37,6 38,9 @@ func FileLayout(th *material.Theme) FileLayoutStyle {
					return InputLayout(th, input, "").Layout(gtx)
				},
			},
			Hover: widgetx.Hover{
				Color: colorx.Hover(th.Palette.Bg),
			},
			Selection: widgetx.SelectFile,
		},
	}

M widgetx/materialx/tree.go => widgetx/materialx/tree.go +5 -5
@@ 1,8 1,6 @@
package materialx

import (
	"image/color"

	"gioui.org/layout"
	"gioui.org/unit"
	"gioui.org/widget"


@@ 20,7 18,7 @@ type TreeLayoutStyle struct {
	// Opened is the icon reflecting the opened state of a tree item.
	Opened *widget.Icon
	// Hover defines the color to be used when hovering over an element.
	Hover color.NRGBA
	Hover widgetx.Hover
	// Size is the size used for Closed and Opened icons.
	// If the size is zero, then non leaf items item are made clickable
	// to be able to open or close the branch.


@@ 37,7 35,7 @@ func TreeLayout(th *material.Theme, t *widgetx.Tree) TreeLayoutStyle {
	return TreeLayoutStyle{
		Closed:  closed,
		Opened:  opened,
		Hover:   colorx.MulAlpha(th.Palette.Bg, 64),
		Hover:   widgetx.Hover{Color: colorx.MulAlpha(th.Palette.Bg, 64)},
		Size:    unit.Dp(20),
		Padding: unit.Dp(20),
		Tree:    t,


@@ 53,7 51,9 @@ func (t TreeLayoutStyle) Layout(gtx layout.Context, el layout.ListElement) layou
		noIcon := t.Size.V == 0
		leafNode := node.ChildrenNum == 0
		hovered := click != nil && click.Hovered()
		hovering := layout.Expanded(widgetx.Hover(t.Hover, hovered))
		hovering := layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			return t.Hover.Layout(gtx, hovered)
		})
		return layout.Flex{
			Axis:      axis,
			Alignment: layout.Middle,