Skip to content

Commit

Permalink
make dropdown extra display a regular dropdown item and refactor
Browse files Browse the repository at this point in the history
Signed-off-by: Philemon Ukane <[email protected]>
  • Loading branch information
ukane-philemon committed Nov 27, 2023
1 parent 6db823b commit b06849e
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 240 deletions.
298 changes: 141 additions & 157 deletions ui/cryptomaterial/dropdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ type DropDown struct {
shadow *Shadow
expandedViewAlignment layout.Direction

// extraDisplay is a custom display that will be added to the end of the
// expanded dropdown view if provided.
extraDisplay func(gtx C) D
FontWeight font.Weight
BorderWidth unit.Dp
BorderColor *color.NRGBA
Expand All @@ -63,6 +60,14 @@ type DropDownItem struct {
// DisplayFn is an alternate display function that can be used to layout the
// item instead of using the default item layout.
DisplayFn func(gtx C) D
// Set to true for items that cannot be selected.
PreventSelection bool
}

// DropDown is like DropdownWithCustomPos but uses default values for
// groupPosition, and dropdownInset parameters.
func (t *Theme) DropDown(items []DropDownItem, group uint, reversePos bool) *DropDown {
return t.DropdownWithCustomPos(items, group, 0, 0, reversePos)
}

// DropdownWithCustomPos returns a dropdown component. {groupPosition} parameter
Expand All @@ -72,16 +77,6 @@ type DropDownItem struct {
// parameter is the left inset for the dropdown if {reversePos} is false, else
// it'll become the right inset for the dropdown.
func (t *Theme) DropdownWithCustomPos(items []DropDownItem, group uint, groupPosition uint, dropdownInset int, reversePos bool) *DropDown {
return t.dropdown(items, group, groupPosition, dropdownInset, reversePos)
}

// DropDown is like DropdownWithCustomPos but uses default values for
// groupPosition, and dropdownInset parameters.
func (t *Theme) DropDown(items []DropDownItem, group uint, reversePos bool) *DropDown {
return t.dropdown(items, group, 0, 0, reversePos)
}

func (t *Theme) dropdown(items []DropDownItem, group uint, groupPosition uint, dropdownInset int, reversePos bool) *DropDown {
d := &DropDown{
theme: t,
expanded: false,
Expand Down Expand Up @@ -141,19 +136,16 @@ func (d *DropDown) Len() int {
return len(d.items)
}

// SetExtraDisplay sets d.extraDisplay. The provided extraDisplay will be shown
// at the end of the expanded dropdown view.
func (d *DropDown) SetExtraDisplay(extraDisplay func(gtx C) D) {
d.extraDisplay = extraDisplay
}

func (d *DropDown) handleEvents() {
if d.expanded {
for i := range d.items {
index := i
for d.items[index].clickable.Clicked() {
d.selectedIndex = index
item := d.items[index]
for item.clickable.Clicked() {
d.expanded = false
if !item.PreventSelection {
d.selectedIndex = index
}
break
}
}
Expand All @@ -172,9 +164,14 @@ func (d *DropDown) Changed() bool {
if d.expanded {
for i := range d.items {
index := i
for d.items[index].clickable.Clicked() {
d.selectedIndex = index
item := d.items[index]
for item.clickable.Clicked() {
d.expanded = false
if item.PreventSelection {
return false
}

d.selectedIndex = index
return true
}
}
Expand All @@ -183,99 +180,6 @@ func (d *DropDown) Changed() bool {
return false
}

func (d *DropDown) layoutActiveIcon(gtx C, index int) D {
var icon *Icon
if !d.expanded {
icon = NewIcon(d.dropdownIcon)
} else if index == d.selectedIndex {
icon = NewIcon(d.navigationIcon)
}

if icon == nil {
return D{} // return early
}

icon.Color = d.theme.Color.Gray1
if d.expanded && d.SelectedItemIconColor != nil {
icon.Color = *d.SelectedItemIconColor
}

return icon.Layout(gtx, values.MarginPadding20)
}

func (d *DropDown) itemLayout(gtx C, index int, clickable *Clickable, item *DropDownItem, radius int, bodyLayout *LinearLayout) D {
padding := values.MarginPadding10
if item.Icon != nil {
padding = values.MarginPadding8
}

return LinearLayout{
Width: MatchParent,
Height: WrapContent,
Clickable: clickable,
Padding: layout.UniformInset(padding),
Border: Border{Radius: Radius(radius)},
}.Layout(gtx,
layout.Rigid(func(gtx C) D {
if item.Icon == nil {
return D{}
}

return item.Icon.Layout24dp(gtx)
}),
layout.Flexed(1, func(gtx C) D {
if item.DisplayFn != nil {
return item.DisplayFn(gtx)
}

return bodyLayout.Layout2(gtx, func(gtx C) D {
lbl := d.theme.Body2(item.Text)
if !d.expanded && len(item.Text) > maxDropdownItemTextLen {
lbl.Text = item.Text[:maxDropdownItemTextLen-3 /* subtract space for the ellipsis */] + "..."
}
lbl.Font.Weight = d.FontWeight
return lbl.Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return d.layoutActiveIcon(gtx, index)
}),
)
}

func (d *DropDown) selectedItemLayout(gtx C) D {
if len(d.items) == 0 {
if d.extraDisplay != nil {
return d.extraDisplay(gtx)
}
return D{}
}

item := d.items[d.selectedIndex]
clickable := d.clickable
clickable.Hoverable = d.MakeSelectedItemHoverableAfterCollapse
bodyLayout := LinearLayout{
Width: MatchParent,
Height: WrapContent,
Padding: layout.Inset{Right: values.MarginPadding5},
Direction: d.SelectedItemDirectionAfterCollapse,
}

return d.itemLayout(gtx, d.selectedIndex, clickable, &item, 8, &bodyLayout)
}

func (d *DropDown) layoutExpandedItem(gtx C, itemIndex int) D {
item := d.items[itemIndex]
body := LinearLayout{
Width: MatchParent,
Height: WrapContent,
Padding: layout.Inset{Right: values.MarginPadding5},
Direction: layout.W,
}

return d.itemLayout(gtx, itemIndex, item.clickable, &item, 8, &body)
}

// defaultDropdownWidth returns the default width for a dropdown depending on
// it's position.
func defaultDropdownWidth(reversePosition bool) unit.Dp {
Expand All @@ -293,38 +197,11 @@ func (d *DropDown) Layout(gtx C) D {
d.handleEvents()

if d.StackBelowCollapsedLayout {
layoutContents := []layout.StackChild{layout.Expanded(func(gtx C) D {
expanded := d.expanded
d.expanded = false // enforce a collapsed layout display before creating the layout Dimensions and undo later.
display := d.collapsedLayout(gtx)
d.expanded = expanded
return display
})}

if d.expanded {
layoutContents = append(layoutContents, layout.Expanded(func(gtx layout.Context) layout.Dimensions {
// Adding d.ExpandedLayoutInset.Top accounts for the the extra
// shift in vertical space set by d.ExpandedLayoutInset.Top to
// ensure the expanded view has enough space for its elements.
maxY := unit.Dp(len(d.items))*values.MarginPadding50 + d.ExpandedLayoutInset.Top
if d.extraDisplay != nil {
maxY += values.MarginPadding50
}

gtx.Constraints.Max.Y = gtx.Dp(maxY)
return d.expandedLayout(gtx)
}))
}

return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layoutContents...)
return d.stackExpandedViewBelowCollapsedView(gtx)
}

if d.GroupPosition == DropdownBasePos && d.isDropdownGroupCollapsed(d.group) {
maxY := unit.Dp(len(d.items)) * values.MarginPadding50
if d.extraDisplay != nil {
maxY += values.MarginPadding50
}

gtx.Constraints.Max.Y = gtx.Dp(maxY)
if d.expanded {
return d.backdrop.Layout(gtx, func(gtx C) D {
Expand All @@ -342,25 +219,52 @@ func (d *DropDown) Layout(gtx C) D {
return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layout.Stacked(d.collapsedLayout))
}

// stackExpandedViewBelowCollapsedView stacks the expanded view right below the
// collapsed view such that both the current selection and the list of items are
// visible.
func (d *DropDown) stackExpandedViewBelowCollapsedView(gtx C) D {
layoutContents := []layout.StackChild{layout.Expanded(func(gtx C) D {
expanded := d.expanded
d.expanded = false // enforce a collapsed layout display before creating the layout Dimensions and undo later.
display := d.collapsedLayout(gtx)
d.expanded = expanded
return display
})}

// No need to display expanded view when there is only one item.
if d.expanded && len(d.items) > 1 {
layoutContents = append(layoutContents, layout.Expanded(func(gtx layout.Context) layout.Dimensions {
// Adding d.ExpandedLayoutInset.Top accounts for the the extra
// shift in vertical space set by d.ExpandedLayoutInset.Top to
// ensure the expanded view has enough space for its elements.
maxY := unit.Dp(len(d.items))*values.MarginPadding50 + d.ExpandedLayoutInset.Top
gtx.Constraints.Max.Y = gtx.Dp(maxY)
return d.expandedLayout(gtx)
}))
}

return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layoutContents...)
}

// expandedLayout computes dropdown layout when dropdown is opened.
func (d *DropDown) expandedLayout(gtx C) D {
listItems := len(d.items)
return d.ExpandedLayoutInset.Layout(gtx, func(gtx C) D {
return d.drawLayout(gtx, func(gtx C) D {
list := &layout.List{Axis: layout.Vertical}
return list.Layout(gtx, listItems, func(gtx C, index int) D {
if (index == listItems-1 || listItems == 0) && d.extraDisplay != nil {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return d.layoutExpandedItem(gtx, index)
}),
layout.Rigid(d.extraDisplay),
)
} else if listItems != 0 {
return d.layoutExpandedItem(gtx, index)
return list.Layout(gtx, len(d.items), func(gtx C, index int) D {
if len(d.items) == 0 {
return D{}
}

return D{}
item := d.items[index]
body := LinearLayout{
Width: MatchParent,
Height: WrapContent,
Padding: layout.Inset{Right: values.MarginPadding5},
Direction: layout.W,
}

return d.itemLayout(gtx, index, item.clickable, &item, 8, &body)
})
})
})
Expand All @@ -369,10 +273,90 @@ func (d *DropDown) expandedLayout(gtx C) D {
// collapsedLayout computes dropdown layout when dropdown is closed.
func (d *DropDown) collapsedLayout(gtx C) D {
return d.collapsedLayoutInset.Layout(gtx, func(gtx C) D {
return d.drawLayout(gtx, d.selectedItemLayout)
return d.drawLayout(gtx, func(gtx C) D {
if len(d.items) == 0 {
return D{}
}

selectedItem := d.items[d.selectedIndex]
bodyLayout := LinearLayout{
Width: MatchParent,
Height: WrapContent,
Padding: layout.Inset{Right: values.MarginPadding5},
Direction: d.SelectedItemDirectionAfterCollapse,
}

// d.MakeSelectedItemHoverableAfterCollapse is set after creating
// the dropdown but before drawing the layout.
d.clickable.Hoverable = d.MakeSelectedItemHoverableAfterCollapse
return d.itemLayout(gtx, d.selectedIndex, d.clickable, &selectedItem, 8, &bodyLayout)
})
})
}

func (d *DropDown) itemLayout(gtx C, index int, clickable *Clickable, item *DropDownItem, radius int, bodyLayout *LinearLayout) D {
padding := values.MarginPadding10
if item.Icon != nil {
padding = values.MarginPadding8
}

return LinearLayout{
Width: MatchParent,
Height: WrapContent,
Clickable: clickable,
Padding: layout.UniformInset(padding),
Border: Border{Radius: Radius(radius)},
}.Layout(gtx,
layout.Rigid(func(gtx C) D {
if item.Icon == nil {
return D{}
}

return item.Icon.Layout24dp(gtx)
}),
layout.Flexed(1, func(gtx C) D {
if item.DisplayFn != nil {
return item.DisplayFn(gtx)
}

return bodyLayout.Layout2(gtx, func(gtx C) D {
lbl := d.theme.Body2(item.Text)
if !d.expanded && len(item.Text) > maxDropdownItemTextLen {
lbl.Text = item.Text[:maxDropdownItemTextLen-3 /* subtract space for the ellipsis */] + "..."
}
lbl.Font.Weight = d.FontWeight
return lbl.Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
if !item.PreventSelection && len(d.items) > 1 {
return d.layoutActiveIcon(gtx, index)
}
return D{}
}),
)
}

func (d *DropDown) layoutActiveIcon(gtx C, index int) D {
var icon *Icon
if !d.expanded {
icon = NewIcon(d.dropdownIcon)
} else if index == d.selectedIndex {
icon = NewIcon(d.navigationIcon)
}

if icon == nil {
return D{} // return early
}

icon.Color = d.theme.Color.Gray1
if d.expanded && d.SelectedItemIconColor != nil {
icon.Color = *d.SelectedItemIconColor
}

return icon.Layout(gtx, values.MarginPadding20)
}

// drawLayout wraps the page tx and sync section in a card layout
func (d *DropDown) drawLayout(gtx C, body layout.Widget) D {
if d.Width <= 0 {
Expand Down
Loading

0 comments on commit b06849e

Please sign in to comment.