commit f959775d4c2a7924868f8afef3c97b981eda9229
parent ec7ef2212d49e91b5b605170e693b44957dfb4ac
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Fri, 13 Nov 2020 17:42:00 +0800
feat(project-rail): sketch of project rail with hardcoded projects labels
Diffstat:
4 files changed, 258 insertions(+), 50 deletions(-)
diff --git a/cmd/kanban/main.go b/cmd/kanban/main.go
@@ -112,6 +112,7 @@ type UI struct {
Kanban *kanban.Kanban
Th *material.Theme
Panels []Panel
+ Rail Rail
TicketStates Map
Modal layout.Widget
TicketForm TicketForm
@@ -122,6 +123,8 @@ type UI struct {
Index int
Stage int
}
+ CreateProjectButton widget.Clickable
+ ProjectForm ProjectForm
}
func (ui *UI) Loop() error {
@@ -182,13 +185,7 @@ func (ui *UI) Update(gtx C) {
for ii := range ui.Panels {
panel := &ui.Panels[ii]
if panel.CreateTicket.Clicked() {
- ui.Modal = func(gtx C) D {
- return Card{
- Title: "Add Ticket",
- }.Layout(gtx, ui.Th, func(gtx C) D {
- return ui.TicketForm.Layout(gtx, ui.Th, panel.Label)
- })
- }
+ ui.AddTicket(panel.Label)
}
}
for s, ok := ui.TicketStates.Next(); ok; s, ok = ui.TicketStates.Next() {
@@ -253,56 +250,95 @@ func (ui *UI) Update(gtx C) {
if ui.TicketDetails.Cancel.Clicked() {
ui.Clear()
}
+ if ui.CreateProjectButton.Clicked() {
+ ui.CreateProject()
+ }
+ if ui.ProjectForm.Cancel.Clicked() {
+ ui.Clear()
+ }
}
func (ui *UI) Layout(gtx C) D {
key.InputOp{Tag: ui}.Add(gtx.Ops)
- return layout.Stack{}.Layout(
+ return layout.Flex{Axis: layout.Horizontal}.Layout(
gtx,
- layout.Stacked(func(gtx C) D {
- ui.TicketStates.Begin()
- var panels = make([]layout.FlexChild, len(ui.Panels))
- for kk := range ui.Panels {
- panel := &ui.Panels[kk]
- panels[kk] = layout.Flexed(1, func(gtx C) D {
- stage, _ := ui.Kanban.Stage(panel.Label)
- var cards = make([]layout.ListElement, len(stage.Tickets))
- for ii, ticket := range stage.Tickets {
- id := strconv.Itoa(ticket.ID)
- cards[ii] = func(gtx C, ii int) D {
- t := (*Ticket)(ui.TicketStates.New(id, unsafe.Pointer(&Ticket{})))
- t.Ticket = stage.Tickets[ii]
- t.Stage = stage.Name
- if ui.FocusedTicket.ID == t.ID {
- return widget.Border{
- Color: color.RGBA{B: 200, A: 200},
- Width: unit.Dp(2),
- }.Layout(gtx, func(gtx C) D {
+ layout.Rigid(func(gtx C) D {
+ gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
+ gtx.Constraints.Max.X = gtx.Px(unit.Dp(80))
+ gtx.Constraints.Min.X = 0
+ return ui.Rail.Layout(
+ gtx,
+ func(gtx C) D {
+ return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
+ btn := material.IconButton(ui.Th, &ui.CreateProjectButton, icons.ContentAdd)
+ btn.Size = unit.Dp(20)
+ btn.Inset = layout.UniformInset(unit.Dp(8))
+ return btn.Layout(gtx)
+ })
+ },
+ Destination("kanban", func(gtx C) D {
+ return material.Label(ui.Th, unit.Dp(16), "Kanban").Layout(gtx)
+ }),
+ Destination("avisha", func(gtx C) D {
+ return material.Label(ui.Th, unit.Dp(16), "Avisha").Layout(gtx)
+ }),
+ Destination("watch", func(gtx C) D {
+ return material.Label(ui.Th, unit.Dp(16), "Watch").Layout(gtx)
+ }),
+ Destination("gopack", func(gtx C) D {
+ return material.Label(ui.Th, unit.Dp(16), "GoPack").Layout(gtx)
+ }),
+ )
+ }),
+ layout.Flexed(1, func(gtx C) D {
+ return layout.Stack{}.Layout(
+ gtx,
+ layout.Stacked(func(gtx C) D {
+ ui.TicketStates.Begin()
+ var panels = make([]layout.FlexChild, len(ui.Panels))
+ for kk := range ui.Panels {
+ panel := &ui.Panels[kk]
+ panels[kk] = layout.Flexed(1, func(gtx C) D {
+ stage, _ := ui.Kanban.Stage(panel.Label)
+ var cards = make([]layout.ListElement, len(stage.Tickets))
+ for ii, ticket := range stage.Tickets {
+ id := strconv.Itoa(ticket.ID)
+ cards[ii] = func(gtx C, ii int) D {
+ t := (*Ticket)(ui.TicketStates.New(id, unsafe.Pointer(&Ticket{})))
+ t.Ticket = stage.Tickets[ii]
+ t.Stage = stage.Name
+ if ui.FocusedTicket.ID == t.ID {
+ return widget.Border{
+ Color: color.RGBA{B: 200, A: 200},
+ Width: unit.Dp(2),
+ }.Layout(gtx, func(gtx C) D {
+ return t.Layout(gtx, ui.Th)
+ })
+ }
return t.Layout(gtx, ui.Th)
- })
+ }
}
- return t.Layout(gtx, ui.Th)
- }
+ return panel.Layout(gtx, ui.Th, cards...)
+ })
}
- return panel.Layout(gtx, ui.Th, cards...)
- })
- }
- return layout.Flex{
- Axis: layout.Horizontal,
- Spacing: layout.SpaceEvenly,
- }.Layout(
- gtx,
- panels...,
+ return layout.Flex{
+ Axis: layout.Horizontal,
+ Spacing: layout.SpaceEvenly,
+ }.Layout(
+ gtx,
+ panels...,
+ )
+ }),
+ layout.Expanded(func(gtx C) D {
+ if ui.Modal == nil {
+ return D{}
+ }
+ return Modal(gtx, func(gtx C) D {
+ return ui.Modal(gtx)
+ })
+ }),
)
}),
- layout.Expanded(func(gtx C) D {
- if ui.Modal == nil {
- return D{}
- }
- return Modal(gtx, func(gtx C) D {
- return ui.Modal(gtx)
- })
- }),
)
}
@@ -367,6 +403,7 @@ func (ui *UI) Refocus(d Direction) {
func (ui *UI) Clear() {
ui.Modal = nil
ui.TicketForm = TicketForm{}
+ ui.ProjectForm = ProjectForm{}
ui.FocusedTicket = struct {
ID int
Index int
@@ -398,6 +435,28 @@ func (ui *UI) EditTicket(t kanban.Ticket) {
}
}
+// AddTicket opens the ticket form for creating ticket data.
+func (ui *UI) AddTicket(stage string) {
+ ui.Modal = func(gtx C) D {
+ return Card{
+ Title: "Add Ticket",
+ }.Layout(gtx, ui.Th, func(gtx C) D {
+ return ui.TicketForm.Layout(gtx, ui.Th, stage)
+ })
+ }
+}
+
+// CreatTicket opens the project creation dialog.
+func (ui *UI) CreateProject() {
+ ui.Modal = func(gtx C) D {
+ return Card{
+ Title: "Create a new Project",
+ }.Layout(gtx, ui.Th, func(gtx C) D {
+ return ui.ProjectForm.Layout(gtx, ui.Th)
+ })
+ }
+}
+
// DeleteTickets opens the confirmation dialog for deleting a ticket.
func (ui *UI) DeleteTicket(t kanban.Ticket) {
ui.DeleteDialog.Ticket = t
@@ -907,3 +966,48 @@ func (t *TicketDetails) Layout(gtx C, th *material.Theme) D {
}),
)
}
+
+// ProjectForm renders a form for manipulating projects.
+type ProjectForm struct {
+ Name materials.TextField
+ Submit widget.Clickable
+ Cancel widget.Clickable
+}
+
+func (form *ProjectForm) Layout(gtx C, th *material.Theme) D {
+ return layout.Flex{
+ Axis: layout.Vertical,
+ }.Layout(
+ gtx,
+ layout.Rigid(func(gtx C) D {
+ return form.Name.Layout(gtx, th, "Project Name")
+ }),
+ layout.Rigid(func(gtx C) D {
+ gtx.Constraints.Min.X = gtx.Constraints.Max.X
+ return layout.Inset{
+ Top: unit.Dp(10),
+ }.Layout(gtx, func(gtx C) D {
+ return layout.Flex{
+ Axis: layout.Horizontal,
+ }.Layout(
+ gtx,
+ layout.Flexed(1, func(gtx C) D {
+ return D{Size: gtx.Constraints.Min}
+ }),
+ layout.Rigid(func(gtx C) D {
+ btn := material.Button(th, &form.Cancel, "Cancel")
+ btn.Color = th.Color.Primary
+ btn.Background = color.RGBA{}
+ return btn.Layout(gtx)
+ }),
+ layout.Rigid(func(gtx C) D {
+ return D{Size: image.Point{X: gtx.Px(unit.Dp(10))}}
+ }),
+ layout.Rigid(func(gtx C) D {
+ return material.Button(th, &form.Submit, "Submit").Layout(gtx)
+ }),
+ )
+ })
+ }),
+ )
+}
diff --git a/cmd/kanban/widget.go b/cmd/kanban/widget.go
@@ -3,6 +3,7 @@ package main
import (
"image"
"image/color"
+ "unsafe"
"gioui.org/f32"
"gioui.org/layout"
@@ -104,3 +105,100 @@ func WithInset(inset layout.Inset) ButtonOption {
btn.ButtonStyle.Inset = inset
}
}
+
+// Rail is an interactive side rail with a list of widget items that can be
+// selected.
+//
+// Typically used as navigation or contextual actions.
+//
+// Rail is stateful.
+type Rail struct {
+ layout.List
+ Map Map
+}
+
+// RailChild is an item that renders in a rail.
+type RailChild struct {
+ Name string
+ W layout.Widget
+}
+
+// Destination is a rail item that represents a navigatable object.
+// Destinations are padded by default.
+func Destination(name string, w layout.Widget) RailChild {
+ return RailChild{
+ Name: name,
+ W: w,
+ }
+}
+
+func (r *Rail) next(key string) *widget.Clickable {
+ return (*widget.Clickable)(r.Map.New(key, unsafe.Pointer(&widget.Clickable{})))
+}
+
+// Layout the rail with the given items.
+func (r *Rail) Layout(gtx C, action layout.Widget, items ...RailChild) D {
+ r.List.Axis = layout.Vertical
+ r.List.Alignment = layout.Middle
+ r.Map.Begin()
+ return layout.Flex{
+ Axis: layout.Vertical,
+ Alignment: layout.Middle,
+ }.Layout(
+ gtx,
+ layout.Rigid(func(gtx C) D {
+ return action(gtx)
+ }),
+ layout.Rigid(func(gtx C) D {
+ return layout.UniformInset(unit.Dp(5)).Layout(gtx, func(gtx C) D {
+ return Div{
+ Color: color.RGBA{R: 220, B: 220, G: 220, A: 255},
+ Length: unit.Px(float32(gtx.Constraints.Max.X)),
+ Thickness: unit.Dp(1),
+ Axis: layout.Horizontal,
+ }.Layout(gtx)
+ })
+ }),
+ layout.Rigid(func(gtx C) D {
+ return r.List.Layout(gtx, len(items), func(gtx C, ii int) D {
+ rc := items[ii]
+ return material.Clickable(gtx, r.next(rc.Name), func(gtx C) D {
+ return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx C) D {
+ gtx.Constraints.Min.X = gtx.Constraints.Max.X
+ return rc.W(gtx)
+ })
+ })
+ })
+ }),
+ )
+}
+
+// Div is a visual divider: a colored line with a thickness.
+type Div struct {
+ Thickness unit.Value
+ Length unit.Value
+ Axis layout.Axis
+ Color color.RGBA
+}
+
+func (d Div) Layout(gtx C) D {
+ // Draw a line as a very line.
+ var sz image.Point
+ switch d.Axis {
+ case layout.Horizontal:
+ sz = image.Point{
+ X: gtx.Px(d.Length),
+ Y: gtx.Px(d.Thickness),
+ }
+ case layout.Vertical:
+ sz = image.Point{
+ X: gtx.Px(d.Thickness),
+ Y: gtx.Px(d.Length),
+ }
+ }
+ return Rect{
+ Color: d.Color,
+ Size: layout.FPt(sz),
+ Radii: 0,
+ }.Layout(gtx)
+}
diff --git a/go.mod b/go.mod
@@ -3,14 +3,14 @@ module git.sr.ht/~jackmordaunt/kanban
go 1.15
require (
- gioui.org v0.0.0-20201110154114-ae256b5be812
- git.sr.ht/~whereswaldon/materials v0.0.0-20201108152851-4af89701ceb6
+ gioui.org v0.0.0-20201112145235-3c739323cb4f
+ git.sr.ht/~whereswaldon/materials v0.0.0-20201111125311-690e8da25ce3
github.com/asdine/storm/v3 v3.2.1
github.com/jackmordaunt/icns v1.0.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7
- golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65 // indirect
+ golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect
golang.org/x/text v0.3.4 // indirect
)
diff --git a/go.sum b/go.sum
@@ -2,8 +2,12 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
gioui.org v0.0.0-20201106195654-dbc0796d0207/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
gioui.org v0.0.0-20201110154114-ae256b5be812 h1:fmETJJuKWcrg5pYKSX/MkwNXv3MBF7LsjBPa4qyO1Cw=
gioui.org v0.0.0-20201110154114-ae256b5be812/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
+gioui.org v0.0.0-20201112145235-3c739323cb4f h1:O1WtE27z2GqOJjE0Dl6k2qqts63tqoSwwoWyE7K3RLM=
+gioui.org v0.0.0-20201112145235-3c739323cb4f/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
git.sr.ht/~whereswaldon/materials v0.0.0-20201108152851-4af89701ceb6 h1:R/9jzikSdVeHbeJx5WU3e4lNRHzxLt2HduMWLa78m5k=
git.sr.ht/~whereswaldon/materials v0.0.0-20201108152851-4af89701ceb6/go.mod h1:TayKd2ixEIPSq7ZzJIb1hcIsV1FapB43GCSx8GnLyUg=
+git.sr.ht/~whereswaldon/materials v0.0.0-20201111125311-690e8da25ce3 h1:885LotAnlGb2X4ZBmaqbkmVpW+KkQJEALwmHcm4WHo8=
+git.sr.ht/~whereswaldon/materials v0.0.0-20201111125311-690e8da25ce3/go.mod h1:C1KEwj50dyQ+TpIaomIgBqhnSbbB34UqeFvuCOuddro=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
@@ -75,6 +79,8 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfru
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65 h1:Qo9oJ566/Sq7N4hrGftVXs8GI2CXBCuOd4S2wHE/e0M=
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
+golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=