Add project components 421-435; Checkbox turns only when on
Fifteen project components: QuestBoard, TaskItem, TaskList, Milestones, Burndown, Pomodoro, Priority, AssigneePicker, LabelPicker, TimeTracker, Roadmap, SprintPanel, IssueCard, Checklist and WorkloadBar. QuestBoard is one base that draws TODO, DOING and DONE; arrows move, Alt with an arrow carries a card, and a drag carries it live. Priority draws 12x12 bars, and Critical is on fire. Pomodoro banks time on PAUSE and the tomato sleeps through the rest. Extreme inputs fail fast or saturate instead of wrapping. MyGo's CheckboxBase turns its value before Disabled is set, so input.Checkbox now hands it a copy and keeps only turns of an enabled base. The home scene test checks the sky row, not one pixel that falling snow can cover.
This commit is contained in:
30 files changed
+2496
-7
No files matched your search
@@ -0,0 +1,113 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/project"
|
||||
)
|
||||
|
||||
var projectEntries = []Entry{
|
||||
{421, "QuestBoard", "任务板", "project", "Quests in three columns. Drag a card across, or hold Alt with the arrows.", demoQuestBoard},
|
||||
{422, "TaskItem", "任务项", "project", "A box to tick, a title, how urgent, and who.", demoTaskItem},
|
||||
{423, "TaskList", "任务列表", "project", "Tasks with OPEN, DONE and URGENT chips to narrow them down.", demoTaskList},
|
||||
{424, "Milestones", "里程碑", "project", "Flags along the road. The little hero stands at today.", demoMilestones},
|
||||
{425, "Burndown", "燃尽图", "project", "The straight line is the plan. The other one is what happened.", demoBurndown},
|
||||
{426, "Pomodoro", "番茄钟", "project", "Twenty-five minutes of work, five of rest. The tomato naps on the break.", demoPomodoro},
|
||||
{427, "Priority", "优先级", "project", "LOW, MEDIUM, HIGH and, for the worst, CRITICAL: it is on fire.", demoPriority},
|
||||
{428, "AssigneePicker", "指派", "project", "The party lines up. Pick who takes the quest, or nobody.", demoAssigneePicker},
|
||||
}
|
||||
|
||||
type projectDemo struct {
|
||||
board [3][]string
|
||||
task project.Task
|
||||
tasks []project.Task
|
||||
assignee, changes int
|
||||
}
|
||||
|
||||
func projectState(c *ui.Context) *projectDemo {
|
||||
return ui.Local(c.Root(), demoKey("project"), func() projectDemo {
|
||||
return projectDemo{
|
||||
board: [3][]string{{"SLAY DRAGON", "FIND THE KEY", "SAVE VILLAGE"}, {"GRIND XP"}, {"TALK TO OLD MAN"}},
|
||||
task: project.Task{Title: "Fix the save bug", Priority: project.High, Assignee: "Brom"},
|
||||
tasks: []project.Task{
|
||||
{Title: "Feed the slime", Priority: project.Low, Assignee: "Aria"},
|
||||
{Title: "Fix the save bug", Priority: project.High, Assignee: "Brom", Done: true},
|
||||
{Title: "Ship it", Priority: project.Critical},
|
||||
{Title: "Draw the map", Priority: project.Medium, Assignee: "Cass"},
|
||||
},
|
||||
assignee: -1,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoQuestBoard(c *ui.Context, log func(string)) {
|
||||
s := projectState(c)
|
||||
if project.QuestBoard(c, "Quests", &s.board, project.QuestBoardOptions{}).Moved {
|
||||
log(fmt.Sprintf("moved: %d to do, %d doing, %d done", len(s.board[0]), len(s.board[1]), len(s.board[2])))
|
||||
}
|
||||
}
|
||||
|
||||
func demoTaskItem(c *ui.Context, log func(string)) {
|
||||
s := projectState(c)
|
||||
if project.TaskItem(c, &s.task, project.TaskItemOptions{}).Changed {
|
||||
log(fmt.Sprintf("done: %v", s.task.Done))
|
||||
}
|
||||
}
|
||||
|
||||
func demoTaskList(c *ui.Context, log func(string)) {
|
||||
s := projectState(c)
|
||||
if project.TaskList(c, "Tasks", s.tasks, project.TaskListOptions{}).Changed {
|
||||
s.changes++
|
||||
log(fmt.Sprintf("change %d", s.changes))
|
||||
}
|
||||
}
|
||||
|
||||
func demoMilestones(c *ui.Context, log func(string)) {
|
||||
now := pixel.K(c).Now()
|
||||
d := func(days int) time.Time { return now.AddDate(0, 0, days) }
|
||||
project.Milestones(c, "Road", []project.Milestone{
|
||||
{Title: "ALPHA", Date: d(-40), Done: true}, {Title: "BETA", Date: d(-12), Done: true},
|
||||
{Title: "DEMO", Date: d(9)}, {Title: "LAUNCH", Date: d(30)}, {Title: "DLC", Date: d(60)},
|
||||
}, project.MilestonesOptions{})
|
||||
}
|
||||
|
||||
func demoBurndown(c *ui.Context, log func(string)) {
|
||||
project.Burndown(c, "Sprint 7", 40, 10, []float64{40, 37, 35, 30, 28, 22}, project.BurndownOptions{})
|
||||
}
|
||||
|
||||
func demoPomodoro(c *ui.Context, log func(string)) {
|
||||
r := project.Pomodoro(c, "Tomato", project.PomodoroOptions{})
|
||||
if r.Changed {
|
||||
log("button")
|
||||
}
|
||||
if r.Rang {
|
||||
log("ring")
|
||||
}
|
||||
}
|
||||
|
||||
func demoPriority(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
ui.Column(c).Gap(k.D(4)).Children(func() {
|
||||
for _, l := range []project.Level{project.Low, project.Medium, project.High, project.Critical} {
|
||||
ui.Row(c).Gap(k.D(6)).AlignItems(ui.Center).Children(func() {
|
||||
project.Priority(c, l, project.PriorityOptions{Scale: 2})
|
||||
pixel.Text(c, k.Msg([]string{"low", "medium", "high", "critical"}[l]), pixel.TextOptions{})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoAssigneePicker(c *ui.Context, log func(string)) {
|
||||
s := projectState(c)
|
||||
if project.AssigneePicker(c, "Party", party[:4], &s.assignee, project.AssigneePickerOptions{}).Changed {
|
||||
who := "nobody"
|
||||
if s.assignee >= 0 {
|
||||
who = party[s.assignee]
|
||||
}
|
||||
log("assigned to " + who)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/project"
|
||||
)
|
||||
|
||||
var teamEntries = []Entry{
|
||||
{429, "LabelPicker", "标签选择", "project", "Labels in slots. Toggle the ones that fit.", demoLabelPicker},
|
||||
{430, "TimeTracker", "计时记录", "project", "START, STOP, and where the hours went.", demoTimeTracker},
|
||||
{431, "Roadmap", "路线图", "project", "Lanes of work laid over the months. The dashed line is today.", demoRoadmap},
|
||||
{432, "SprintPanel", "迭代", "project", "Points done and days left. No pressure.", demoSprintPanel},
|
||||
{433, "IssueCard", "问题卡", "project", "An issue at a glance: number, title, labels, owner. Click to open.", demoIssueCard},
|
||||
{434, "Checklist", "清单", "project", "Small steps under a bar. Finish them all for a fanfare.", demoChecklist},
|
||||
{435, "WorkloadBar", "工作量", "project", "Hours per party member. Gold is busy; red is too much.", demoWorkloadBar},
|
||||
}
|
||||
|
||||
var tags = []project.Label{
|
||||
{Name: "BUG", Slot: pixel.Bad}, {Name: "FEATURE", Slot: pixel.Good}, {Name: "DOCS", Slot: pixel.Info},
|
||||
{Name: "UI", Slot: pixel.Magic}, {Name: "SPEED", Slot: pixel.Warn}, {Name: "LORE", Slot: pixel.Accent},
|
||||
}
|
||||
|
||||
type teamDemo struct {
|
||||
on []bool
|
||||
entries []project.TimeEntry
|
||||
steps []bool
|
||||
}
|
||||
|
||||
func teamState(c *ui.Context) *teamDemo {
|
||||
now := pixel.K(c).Now()
|
||||
return ui.Local(c.Root(), demoKey("team"), func() teamDemo {
|
||||
return teamDemo{
|
||||
on: []bool{true, false, false, true, false, false},
|
||||
entries: []project.TimeEntry{
|
||||
{Task: "GRIND XP", Start: now.Add(-3 * time.Hour), Stop: now.Add(-2*time.Hour - 35*time.Minute)},
|
||||
{Task: "MAP THE CAVE", Start: now.Add(-90 * time.Minute), Stop: now.Add(-48 * time.Minute)},
|
||||
},
|
||||
steps: []bool{true, true, false, false},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoLabelPicker(c *ui.Context, log func(string)) {
|
||||
s := teamState(c)
|
||||
if project.LabelPicker(c, "Labels", tags, s.on, project.LabelPickerOptions{}).Changed {
|
||||
n := 0
|
||||
for _, on := range s.on {
|
||||
if on {
|
||||
n++
|
||||
}
|
||||
}
|
||||
log(fmt.Sprintf("%d labels on", n))
|
||||
}
|
||||
}
|
||||
|
||||
func demoTimeTracker(c *ui.Context, log func(string)) {
|
||||
s := teamState(c)
|
||||
r := project.TimeTracker(c, "Time", "GRIND XP", &s.entries, project.TimeTrackerOptions{})
|
||||
if r.Started {
|
||||
log("started")
|
||||
}
|
||||
if r.Stopped {
|
||||
log("stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func demoRoadmap(c *ui.Context, log func(string)) {
|
||||
now := pixel.K(c).Now()
|
||||
from := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).AddDate(0, -1, 0)
|
||||
m := func(month, day int) time.Time { return from.AddDate(0, month, day) }
|
||||
project.Roadmap(c, "Plan", from, []project.Lane{
|
||||
{Name: "ENGINE", Bars: []project.Bar{{Title: "PALETTES", Start: m(0, 0), End: m(1, 10)}, {Title: "SPRITES", Start: m(1, 14), End: m(3, 0)}}},
|
||||
{Name: "GAME", Bars: []project.Bar{{Title: "LEVEL 1", Start: m(1, 0), End: m(4, 0)}}},
|
||||
{Name: "SOUND", Bars: []project.Bar{{Title: "CHIPTUNE", Start: m(3, 10), End: m(5, 20)}}},
|
||||
}, project.RoadmapOptions{})
|
||||
}
|
||||
|
||||
func demoSprintPanel(c *ui.Context, log func(string)) {
|
||||
now := pixel.K(c).Now()
|
||||
project.SprintPanel(c, project.Sprint{Name: "SPRINT 7: THE DUNGEON", Start: now.AddDate(0, 0, -9), End: now.AddDate(0, 0, 4), Done: 18, Total: 30}, project.SprintPanelOptions{})
|
||||
}
|
||||
|
||||
func demoIssueCard(c *ui.Context, log func(string)) {
|
||||
is := project.Issue{ID: "#42", Title: "Save file vanishes after the boss", Labels: tags[:1], Assignee: "Brom", Priority: project.Critical}
|
||||
if project.IssueCard(c, is, project.IssueCardOptions{}).Clicked {
|
||||
log("open " + is.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func demoChecklist(c *ui.Context, log func(string)) {
|
||||
s := teamState(c)
|
||||
if project.Checklist(c, "Before the quest", []string{"Sharpen the sword", "Pack potions", "Tell mom", "Leave the village"}, s.steps, project.ChecklistOptions{}).Changed {
|
||||
log(fmt.Sprintf("steps %v", s.steps))
|
||||
}
|
||||
}
|
||||
|
||||
func demoWorkloadBar(c *ui.Context, log func(string)) {
|
||||
project.WorkloadBar(c, "Load", []project.Load{{Name: "Aria", Hours: 32}, {Name: "Brom", Hours: 44}, {Name: "Cass", Hours: 18}, {Name: "Dax", Hours: 39}}, project.WorkloadBarOptions{})
|
||||
}
|
||||
+11
-4
@@ -76,10 +76,17 @@ func TestHomeSceneFollowsThePaletteStrip(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatalf("the scene kept its season: %q", tt.Texts())
|
||||
}
|
||||
// The banner's sky starts in Winter Night's darkest slot.
|
||||
got, want := tt.Image().RGBAAt(int(r.X)+1, int(r.Y)+1), pixel.WinterNight.Colors[pixel.Well]
|
||||
if got.R != want.R || got.G != want.G || got.B != want.B {
|
||||
t.Fatalf("the banner's sky is %v, want %v", got, want)
|
||||
// The sky's top row is Winter Night's Well, snow aside.
|
||||
want, well, n := pixel.WinterNight.Colors[pixel.Well], 0, 0
|
||||
for x := int(r.X); x < int(r.X+r.W); x += 2 {
|
||||
got := tt.Image().RGBAAt(x, int(r.Y)+1)
|
||||
if got.R == want.R && got.G == want.G && got.B == want.B {
|
||||
well++
|
||||
}
|
||||
n++
|
||||
}
|
||||
if well*4 < n*3 {
|
||||
t.Fatalf("only %d of %d sky dots are Winter Night's Well", well, n)
|
||||
}
|
||||
if _, ok := tt.Find("Winter Night, in use"); !ok {
|
||||
t.Fatal("the strip does not mark the palette in use")
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
func TestProjectDemosLogTheirActions(t *testing.T) {
|
||||
// Clicks that only focus a component may log nothing.
|
||||
quiet := map[string]bool{"Quests": true}
|
||||
for _, tc := range []struct {
|
||||
path string
|
||||
steps []string
|
||||
}{
|
||||
{"/c/421-questboard", []string{"Quests", "alt:Right"}}, {"/c/422-taskitem", []string{"Fix the save bug"}},
|
||||
{"/c/423-tasklist", []string{"OPEN"}}, {"/c/426-pomodoro", []string{"START", "PAUSE"}},
|
||||
{"/c/428-assigneepicker", []string{"Brom"}}, {"/c/429-labelpicker", []string{"Labels"}},
|
||||
{"/c/430-timetracker", []string{"START", "STOP"}}, {"/c/433-issuecard", []string{"#42 Save file vanishes after the boss"}},
|
||||
{"/c/434-checklist", []string{"Tell mom"}},
|
||||
} {
|
||||
a, tt := open(tc.path)
|
||||
slug := tc.path[3:]
|
||||
for _, step := range tc.steps {
|
||||
before := len(a.logs[slug])
|
||||
if k, ok := strings.CutPrefix(step, "alt:"); ok {
|
||||
tt.Key(ui.Alt, letters[k])
|
||||
} else {
|
||||
click(t, tt, step)
|
||||
}
|
||||
if len(a.logs[slug]) <= before && !quiet[step] {
|
||||
t.Errorf("%s: %s logged nothing: %q", slug, step, a.logs[slug])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -56,7 +56,7 @@ var Categories = []Category{
|
||||
{"tabletop", "Tabletop", "桌游", icon.Crown},
|
||||
}
|
||||
|
||||
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries, codeEntries, debugEntries, accountEntries, playerEntries, filesEntries, storageEntries)
|
||||
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries, codeEntries, debugEntries, accountEntries, playerEntries, filesEntries, storageEntries, projectEntries, teamEntries)
|
||||
|
||||
func Entries() []Entry { return entries }
|
||||
|
||||
|
||||
Reference in new issue
Block a user