| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- package giu
- import (
- "fmt"
- "image"
- "time"
- "github.com/AllenDang/imgui-go"
- )
- var _ Widget = &HSplitterWidget{}
- type HSplitterWidget struct {
- id string
- width float32
- height float32
- delta *float32
- }
- func HSplitter(delta *float32) *HSplitterWidget {
- return &HSplitterWidget{
- id: GenAutoID("HSplitter"),
- width: 0,
- height: 0,
- delta: delta,
- }
- }
- func (h *HSplitterWidget) Size(width, height float32) *HSplitterWidget {
- aw, ah := GetAvailableRegion()
- if width == 0 {
- h.width = aw
- } else {
- h.width = width
- }
- if height == 0 {
- h.height = ah
- } else {
- h.height = height
- }
- return h
- }
- func (h *HSplitterWidget) ID(id string) *HSplitterWidget {
- h.id = id
- return h
- }
- // Build implements Widget interface
- // nolint:dupl // will fix later
- func (h *HSplitterWidget) Build() {
- // Calc line position.
- width := 40
- height := 2
- pt := GetCursorScreenPos()
- centerX := int(h.width / 2)
- centerY := int(h.height / 2)
- ptMin := image.Pt(centerX-width/2, centerY-height/2)
- ptMax := image.Pt(centerX+width/2, centerY+height/2)
- style := imgui.CurrentStyle()
- c := Vec4ToRGBA(style.GetColor(imgui.StyleColorScrollbarGrab))
- // Place a invisible button to capture event.
- imgui.InvisibleButton(h.id, imgui.Vec2{X: h.width, Y: h.height})
- if imgui.IsItemActive() {
- *(h.delta) = imgui.CurrentIO().GetMouseDelta().Y
- } else {
- *(h.delta) = 0
- }
- if imgui.IsItemHovered() {
- imgui.SetMouseCursor(imgui.MouseCursorResizeNS)
- c = Vec4ToRGBA(style.GetColor(imgui.StyleColorScrollbarGrabActive))
- }
- // Draw a line in the very center
- canvas := GetCanvas()
- canvas.AddRectFilled(pt.Add(ptMin), pt.Add(ptMax), c, 0, 0)
- }
- var _ Widget = &VSplitterWidget{}
- type VSplitterWidget struct {
- id string
- width float32
- height float32
- delta *float32
- }
- func VSplitter(delta *float32) *VSplitterWidget {
- return &VSplitterWidget{
- id: GenAutoID("VSplitter"),
- width: 0,
- height: 0,
- delta: delta,
- }
- }
- func (v *VSplitterWidget) Size(width, height float32) *VSplitterWidget {
- aw, ah := GetAvailableRegion()
- if width == 0 {
- v.width = aw
- } else {
- v.width = width
- }
- if height == 0 {
- v.height = ah
- } else {
- v.height = height
- }
- return v
- }
- func (v *VSplitterWidget) ID(id string) *VSplitterWidget {
- v.id = id
- return v
- }
- // Build implements Widget interface
- // nolint:dupl // will fix later
- func (v *VSplitterWidget) Build() {
- // Calc line position.
- width := 2
- height := 40
- pt := GetCursorScreenPos()
- centerX := int(v.width / 2)
- centerY := int(v.height / 2)
- ptMin := image.Pt(centerX-width/2, centerY-height/2)
- ptMax := image.Pt(centerX+width/2, centerY+height/2)
- style := imgui.CurrentStyle()
- c := Vec4ToRGBA(style.GetColor(imgui.StyleColorScrollbarGrab))
- // Place a invisible button to capture event.
- imgui.InvisibleButton(v.id, imgui.Vec2{X: v.width, Y: v.height})
- if imgui.IsItemActive() {
- *(v.delta) = imgui.CurrentIO().GetMouseDelta().X
- } else {
- *(v.delta) = 0
- }
- if imgui.IsItemHovered() {
- imgui.SetMouseCursor(imgui.MouseCursorResizeEW)
- c = Vec4ToRGBA(style.GetColor(imgui.StyleColorScrollbarGrabActive))
- }
- // Draw a line in the very center
- canvas := GetCanvas()
- canvas.AddRectFilled(pt.Add(ptMin), pt.Add(ptMax), c, 0, 0)
- }
- type TreeTableRowWidget struct {
- label string
- flags TreeNodeFlags
- layout Layout
- children []*TreeTableRowWidget
- }
- func TreeTableRow(label string, widgets ...Widget) *TreeTableRowWidget {
- return &TreeTableRowWidget{
- label: GenAutoID(label),
- layout: widgets,
- }
- }
- func (ttr *TreeTableRowWidget) Children(rows ...*TreeTableRowWidget) *TreeTableRowWidget {
- ttr.children = rows
- return ttr
- }
- func (ttr *TreeTableRowWidget) Flags(flags TreeNodeFlags) *TreeTableRowWidget {
- ttr.flags = flags
- return ttr
- }
- // BuildTreeTableRow executes table row building steps.
- func (ttr *TreeTableRowWidget) BuildTreeTableRow() {
- imgui.TableNextRow(0, 0)
- imgui.TableNextColumn()
- open := false
- if len(ttr.children) > 0 {
- open = imgui.TreeNodeV(tStr(ttr.label), int(ttr.flags))
- } else {
- ttr.flags |= TreeNodeFlagsLeaf | TreeNodeFlagsNoTreePushOnOpen
- imgui.TreeNodeV(tStr(ttr.label), int(ttr.flags))
- }
- for _, w := range ttr.layout {
- switch w.(type) {
- case *TooltipWidget,
- *ContextMenuWidget, *PopupModalWidget:
- // noop
- default:
- imgui.TableNextColumn()
- }
- w.Build()
- }
- if len(ttr.children) > 0 && open {
- for _, c := range ttr.children {
- c.BuildTreeTableRow()
- }
- imgui.TreePop()
- }
- }
- var _ Widget = &TreeTableWidget{}
- type TreeTableWidget struct {
- id string
- flags TableFlags
- size imgui.Vec2
- columns []*TableColumnWidget
- rows []*TreeTableRowWidget
- freezeRow int
- freezeColumn int
- }
- func TreeTable() *TreeTableWidget {
- return &TreeTableWidget{
- id: GenAutoID("TreeTable"),
- flags: TableFlagsBordersV | TableFlagsBordersOuterH | TableFlagsResizable | TableFlagsRowBg | TableFlagsNoBordersInBody,
- rows: nil,
- columns: nil,
- }
- }
- // Freeze columns/rows so they stay visible when scrolled.
- func (tt *TreeTableWidget) Freeze(col, row int) *TreeTableWidget {
- tt.freezeColumn = col
- tt.freezeRow = row
- return tt
- }
- func (tt *TreeTableWidget) Size(width, height float32) *TreeTableWidget {
- tt.size = imgui.Vec2{X: width, Y: height}
- return tt
- }
- func (tt *TreeTableWidget) Flags(flags TableFlags) *TreeTableWidget {
- tt.flags = flags
- return tt
- }
- func (tt *TreeTableWidget) Columns(cols ...*TableColumnWidget) *TreeTableWidget {
- tt.columns = cols
- return tt
- }
- func (tt *TreeTableWidget) Rows(rows ...*TreeTableRowWidget) *TreeTableWidget {
- tt.rows = rows
- return tt
- }
- // Build implements Widget interface.
- func (tt *TreeTableWidget) Build() {
- if len(tt.rows) == 0 {
- return
- }
- colCount := len(tt.columns)
- if colCount == 0 {
- colCount = len(tt.rows[0].layout) + 1
- }
- if imgui.BeginTable(tt.id, colCount, imgui.TableFlags(tt.flags), tt.size, 0) {
- if tt.freezeColumn >= 0 && tt.freezeRow >= 0 {
- imgui.TableSetupScrollFreeze(tt.freezeColumn, tt.freezeRow)
- }
- if len(tt.columns) > 0 {
- for _, col := range tt.columns {
- col.BuildTableColumn()
- }
- imgui.TableHeadersRow()
- }
- for _, row := range tt.rows {
- row.BuildTreeTableRow()
- }
- imgui.EndTable()
- }
- }
- var _ Widget = &CustomWidget{}
- type CustomWidget struct {
- builder func()
- }
- // Build implements Widget interface.
- func (c *CustomWidget) Build() {
- if c.builder != nil {
- c.builder()
- }
- }
- func Custom(builder func()) *CustomWidget {
- return &CustomWidget{
- builder: builder,
- }
- }
- var _ Widget = &ConditionWidget{}
- type ConditionWidget struct {
- cond bool
- layoutIf Layout
- layoutElse Layout
- }
- func Condition(cond bool, layoutIf, layoutElse Layout) *ConditionWidget {
- return &ConditionWidget{
- cond: cond,
- layoutIf: layoutIf,
- layoutElse: layoutElse,
- }
- }
- // Build implements Widget interface.
- func (c *ConditionWidget) Build() {
- if c.cond {
- if c.layoutIf != nil {
- c.layoutIf.Build()
- }
- } else {
- if c.layoutElse != nil {
- c.layoutElse.Build()
- }
- }
- }
- // RangeBuilder batch create widgets and render only which is visible.
- func RangeBuilder(id string, values []any, builder func(int, any) Widget) Layout {
- var layout Layout
- layout = append(layout, Custom(func() { imgui.PushID(id) }))
- if len(values) > 0 && builder != nil {
- for i, v := range values {
- valueRef := v
- widget := builder(i, valueRef)
- layout = append(layout, widget)
- }
- }
- layout = append(layout, Custom(func() { imgui.PopID() }))
- return layout
- }
- type ListBoxState struct {
- selectedIndex int
- }
- func (s *ListBoxState) Dispose() {
- // Nothing to do here.
- }
- var _ Widget = &ListBoxWidget{}
- type ListBoxWidget struct {
- id string
- width float32
- height float32
- border bool
- items []string
- menus []string
- onChange func(selectedIndex int)
- onDClick func(selectedIndex int)
- onMenu func(selectedIndex int, menu string)
- }
- func ListBox(id string, items []string) *ListBoxWidget {
- return &ListBoxWidget{
- id: id,
- width: 0,
- height: 0,
- border: true,
- items: items,
- menus: nil,
- onChange: nil,
- onDClick: nil,
- onMenu: nil,
- }
- }
- func (l *ListBoxWidget) Size(width, height float32) *ListBoxWidget {
- l.width, l.height = width, height
- return l
- }
- func (l *ListBoxWidget) Border(b bool) *ListBoxWidget {
- l.border = b
- return l
- }
- func (l *ListBoxWidget) ContextMenu(menuItems []string) *ListBoxWidget {
- l.menus = menuItems
- return l
- }
- func (l *ListBoxWidget) OnChange(onChange func(selectedIndex int)) *ListBoxWidget {
- l.onChange = onChange
- return l
- }
- func (l *ListBoxWidget) OnDClick(onDClick func(selectedIndex int)) *ListBoxWidget {
- l.onDClick = onDClick
- return l
- }
- func (l *ListBoxWidget) OnMenu(onMenu func(selectedIndex int, menu string)) *ListBoxWidget {
- l.onMenu = onMenu
- return l
- }
- // Build implements Widget interface
- // nolint:gocognit // will fix later
- func (l *ListBoxWidget) Build() {
- var state *ListBoxState
- if s := Context.GetState(l.id); s == nil {
- state = &ListBoxState{selectedIndex: 0}
- Context.SetState(l.id, state)
- } else {
- var isOk bool
- state, isOk = s.(*ListBoxState)
- Assert(isOk, "ListBoxWidget", "Build", "wrong state type recovered")
- }
- child := Child().Border(l.border).Size(l.width, l.height).Layout(Layout{
- Custom(func() {
- clipper := imgui.NewListClipper()
- defer clipper.Delete()
- clipper.Begin(len(l.items))
- for clipper.Step() {
- for i := clipper.DisplayStart(); i < clipper.DisplayEnd(); i++ {
- selected := i == state.selectedIndex
- item := l.items[i]
- Selectable(item).Selected(selected).Flags(SelectableFlagsAllowDoubleClick).OnClick(func() {
- if state.selectedIndex != i {
- state.selectedIndex = i
- if l.onChange != nil {
- l.onChange(i)
- }
- }
- }).Build()
- if IsItemHovered() && IsMouseDoubleClicked(MouseButtonLeft) && l.onDClick != nil {
- l.onDClick(state.selectedIndex)
- }
- // Build context menus
- var menus Layout
- for _, m := range l.menus {
- index := i
- menu := m
- menus = append(menus, MenuItem(fmt.Sprintf("%s##%d", menu, index)).OnClick(func() {
- if l.onMenu != nil {
- l.onMenu(index, menu)
- }
- }))
- }
- if len(menus) > 0 {
- ContextMenu().Layout(menus).Build()
- }
- }
- }
- clipper.End()
- }),
- })
- child.Build()
- }
- var _ Widget = &DatePickerWidget{}
- type DatePickerWidget struct {
- id string
- date *time.Time
- width float32
- onChange func()
- format string
- startOfWeek time.Weekday
- }
- func DatePicker(id string, date *time.Time) *DatePickerWidget {
- return &DatePickerWidget{
- id: GenAutoID(id),
- date: date,
- width: 100,
- startOfWeek: time.Sunday,
- onChange: func() {}, // small hack - prevent giu from setting nil cb (skip nil check later)
- }
- }
- func (d *DatePickerWidget) Size(width float32) *DatePickerWidget {
- d.width = width
- return d
- }
- func (d *DatePickerWidget) OnChange(onChange func()) *DatePickerWidget {
- if onChange != nil {
- d.onChange = onChange
- }
- return d
- }
- func (d *DatePickerWidget) Format(format string) *DatePickerWidget {
- d.format = format
- return d
- }
- func (d *DatePickerWidget) StartOfWeek(weekday time.Weekday) *DatePickerWidget {
- d.startOfWeek = weekday
- return d
- }
- func (d *DatePickerWidget) getFormat() string {
- if d.format == "" {
- return "2006-01-02" // default
- }
- return d.format
- }
- func (d *DatePickerWidget) offsetDay(offset int) time.Weekday {
- day := (int(d.startOfWeek) + offset) % 7
- // offset may be negative, thus day can be negative
- day = (day + 7) % 7
- return time.Weekday(day)
- }
- // Build implements Widget interface.
- func (d *DatePickerWidget) Build() {
- if d.date == nil {
- return
- }
- imgui.PushID(d.id)
- defer imgui.PopID()
- if d.width > 0 {
- PushItemWidth(d.width)
- defer PopItemWidth()
- }
- if imgui.BeginComboV(d.id+"##Combo", d.date.Format(d.getFormat()), imgui.ComboFlagsHeightLargest) {
- // --- [Build year widget] ---
- imgui.AlignTextToFramePadding()
- const yearButtonSize = 25
- Row(
- Label(tStr(" Year")),
- Labelf("%14d", d.date.Year()),
- Button("-##"+d.id+"year").OnClick(func() {
- *d.date = d.date.AddDate(-1, 0, 0)
- d.onChange()
- }).Size(yearButtonSize, yearButtonSize),
- Button("+##"+d.id+"year").OnClick(func() {
- *d.date = d.date.AddDate(1, 0, 0)
- d.onChange()
- }).Size(yearButtonSize, yearButtonSize),
- ).Build()
- // --- [Build month widgets] ---
- Row(
- Label("Month"),
- Labelf("%10s(%02d)", d.date.Month().String(), d.date.Month()),
- Button("-##"+d.id+"month").OnClick(func() {
- *d.date = d.date.AddDate(0, -1, 0)
- d.onChange()
- }).Size(yearButtonSize, yearButtonSize),
- Button("+##"+d.id+"month").OnClick(func() {
- *d.date = d.date.AddDate(0, 1, 0)
- d.onChange()
- }).Size(yearButtonSize, yearButtonSize),
- ).Build()
- // --- [Build day widgets] ---
- days := d.getDaysGroups()
- // Create calendar (widget)
- columns := make([]*TableColumnWidget, 7)
- for i := 0; i < 7; i++ {
- firstChar := d.offsetDay(i).String()[0:1]
- columns[i] = TableColumn(firstChar)
- }
- // Build day widgets
- var rows []*TableRowWidget
- for _, week := range days {
- var row []Widget
- for _, day := range week {
- day := day // hack for golang ranges
- if day == 0 {
- row = append(row, Label(" "))
- continue
- }
- row = append(row, d.calendarField(day))
- }
- rows = append(rows, TableRow(row...))
- }
- Table().Flags(TableFlagsBorders | TableFlagsSizingStretchSame).Columns(columns...).Rows(rows...).Build()
- imgui.EndCombo()
- }
- }
- // store month days sorted in weeks.
- func (d *DatePickerWidget) getDaysGroups() (days [][]int) {
- firstDay := time.Date(d.date.Year(), d.date.Month(), 1, 0, 0, 0, 0, time.Local)
- lastDay := firstDay.AddDate(0, 1, 0).Add(time.Nanosecond * -1)
- // calculate first week
- days = append(days, make([]int, 7))
- monthDay := 1
- emptyDaysInFirstWeek := (int(firstDay.Weekday()) - int(d.startOfWeek) + 7) % 7
- for i := emptyDaysInFirstWeek; i < 7; i++ {
- days[0][i] = monthDay
- monthDay++
- }
- // Build rest rows
- for ; monthDay <= lastDay.Day(); monthDay++ {
- if len(days[len(days)-1]) == 7 {
- days = append(days, []int{})
- }
- days[len(days)-1] = append(days[len(days)-1], monthDay)
- }
- // Pad last row
- lastRowLen := len(days[len(days)-1])
- if lastRowLen < 7 {
- for i := lastRowLen; i < 7; i++ {
- days[len(days)-1] = append(days[len(days)-1], 0)
- }
- }
- return days
- }
- func (d *DatePickerWidget) calendarField(day int) Widget {
- today := time.Now()
- highlightColor := imgui.CurrentStyle().GetColor(imgui.StyleColorPlotHistogram)
- return Custom(func() {
- isToday := d.date.Year() == today.Year() && d.date.Month() == today.Month() && day == today.Day()
- if isToday {
- imgui.PushStyleColor(imgui.StyleColorText, highlightColor)
- }
- Selectable(fmt.Sprintf("%02d", day)).Selected(isToday).OnClick(func() {
- *d.date = time.Date(
- d.date.Year(), d.date.Month(), day,
- 0, 0, 0, 0,
- d.date.Location())
- d.onChange()
- }).Build()
- if isToday {
- imgui.PopStyleColor()
- }
- })
- }
|