Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add slide action to slider and Update layout on overview page #256

Merged
merged 16 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 75 additions & 5 deletions ui/cryptomaterial/segmented_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import (
"github.com/crypto-power/cryptopower/ui/values"
)

type SegmentType int

const (
Group SegmentType = iota
Split
)

type SegmentedControl struct {
theme *Theme
list *ClickableList
Expand All @@ -21,22 +28,65 @@ type SegmentedControl struct {

changed bool
mu sync.Mutex

isEnableSwipe bool
JustinBeBoy marked this conversation as resolved.
Show resolved Hide resolved
sliceAction SliceAction
segmentType SegmentType
}

func (t *Theme) SegmentedControl(segmentTitles []string) *SegmentedControl {
func (t *Theme) SegmentedControl(segmentTitles []string, segmentType SegmentType) *SegmentedControl {
list := t.NewClickableList(layout.Horizontal)
list.IsHoverable = false

return &SegmentedControl{
sc := &SegmentedControl{
list: list,
theme: t,
segmentTitles: segmentTitles,
leftNavBtn: t.NewClickable(false),
rightNavBtn: t.NewClickable(false),
isEnableSwipe: true,
JustinBeBoy marked this conversation as resolved.
Show resolved Hide resolved
segmentType: segmentType,
}

sc.sliceAction.Draged(func(dragDirection SwipeDirection) {
isNext := dragDirection == SwipeLeft
sc.handleActionEvent(isNext)
})

return sc
}

func (sc *SegmentedControl) SetEnableSwipe(enable bool) {
sc.isEnableSwipe = enable
}

func (sc *SegmentedControl) Layout(gtx C) D {
func (sc *SegmentedControl) Layout(gtx C, body func(gtx C) D) D {
return UniformPadding(gtx, func(gtx C) D {
return layout.Flex{
Axis: layout.Vertical,
Alignment: layout.Middle,
}.Layout(gtx,
layout.Rigid(func(gtx C) D {
if sc.segmentType == Group {
return sc.GroupTileLayout(gtx)
}
return sc.splitTileLayout(gtx)
}),
layout.Rigid(func(gtx C) D {
return layout.Inset{Top: values.MarginPadding16}.Layout(gtx, func(gtx C) D {
if sc.isEnableSwipe {
return sc.sliceAction.DragLayout(gtx, func(gtx C) D {
return sc.sliceAction.TransformLayout(gtx, body)
}, true)
}
return body(gtx)
})
}),
)
})
}

func (sc *SegmentedControl) GroupTileLayout(gtx C) D {
sc.handleEvents()

return LinearLayout{
Expand Down Expand Up @@ -71,10 +121,10 @@ func (sc *SegmentedControl) Layout(gtx C) D {
)
}

func (sc *SegmentedControl) TransparentLayout(gtx C) D {
func (sc *SegmentedControl) splitTileLayout(gtx C) D {
sc.handleEvents()
return LinearLayout{
Width: gtx.Dp(values.MarginPadding600),
Width: gtx.Dp(values.MarginPadding700),
JustinBeBoy marked this conversation as resolved.
Show resolved Hide resolved
Height: WrapContent,
Orientation: layout.Horizontal,
Alignment: layout.Middle,
Expand Down Expand Up @@ -176,3 +226,23 @@ func (sc *SegmentedControl) SetSelectedSegment(segment string) {
}
}
}

func (sc *SegmentedControl) handleActionEvent(isNext bool) {
l := len(sc.segmentTitles) - 1 // index starts at 0
if isNext {
if sc.selectedIndex == l {
sc.selectedIndex = 0
} else {
sc.selectedIndex++
}
sc.sliceAction.PushLeft()
} else {
if sc.selectedIndex == 0 {
sc.selectedIndex = l
} else {
sc.selectedIndex--
}
sc.sliceAction.PushRight()
}
sc.changed = true
}
187 changes: 187 additions & 0 deletions ui/cryptomaterial/slide_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package cryptomaterial

import (
"image"
"time"

"gioui.org/f32"
"gioui.org/gesture"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
)

const defaultDuration = 300 * time.Millisecond

type SwipeDirection int

const (
SwipeLeft SwipeDirection = iota
SwipeRight
)

type Dragged func(dragDirection SwipeDirection)

type SliceAction struct {
JustinBeBoy marked this conversation as resolved.
Show resolved Hide resolved
duration time.Duration
push int
next *op.Ops
nextCall op.CallOp
lastCall op.CallOp

t0 time.Time
JustinBeBoy marked this conversation as resolved.
Show resolved Hide resolved
offset float32

// animation state
dragStarted f32.Point
dragOffset int
drag gesture.Drag
draged Dragged
isPushing bool
}

// PushLeft pushes the existing widget to the left.
func (s *SliceAction) PushLeft() { s.push = 1 }

// PushRight pushes the existing widget to the right.
func (s *SliceAction) PushRight() { s.push = -1 }

func (s *SliceAction) Draged(drag Dragged) {
s.draged = drag
}

func (s *SliceAction) DragLayout(gtx C, w layout.Widget, isWrapContent bool) D {
for _, event := range s.drag.Events(gtx.Metric, gtx.Queue, gesture.Horizontal) {
switch event.Type {
case pointer.Press:
s.dragStarted = event.Position
s.dragOffset = 0
case pointer.Drag:
newOffset := int(s.dragStarted.X - event.Position.X)
if newOffset > 100 {
if !s.isPushing && s.draged != nil {
s.isPushing = true
s.draged(SwipeLeft)
}
} else if newOffset < -100 {
if !s.isPushing && s.draged != nil {
s.isPushing = true
s.draged(SwipeRight)
}
}
s.dragOffset = newOffset
case pointer.Release:
fallthrough
case pointer.Cancel:
s.isPushing = false
}
}

if isWrapContent {
area := clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops)
s.drag.Add(gtx.Ops)
defer area.Pop()
} else {
s.drag.Add(gtx.Ops)
}

return w(gtx)
}

func (s *SliceAction) TransformLayout(gtx C, w layout.Widget) D {
if s.push != 0 {
s.next = nil
s.lastCall = s.nextCall
s.offset = float32(s.push)
s.t0 = gtx.Now
s.push = 0
}

var delta time.Duration
if !s.t0.IsZero() {
now := gtx.Now
delta = now.Sub(s.t0)
s.t0 = now
}

if s.offset != 0 {
duration := s.duration
if duration == 0 {
duration = defaultDuration
}
movement := float32(delta.Seconds()) / float32(duration.Seconds())
if s.offset < 0 {
s.offset += movement
if s.offset >= 0 {
s.offset = 0
}
} else {
s.offset -= movement
if s.offset <= 0 {
s.offset = 0
}
}

op.InvalidateOp{}.Add(gtx.Ops)
}

var dims layout.Dimensions
{
if s.next == nil {
s.next = new(op.Ops)
}
gtx := gtx
gtx.Ops = s.next
gtx.Ops.Reset()
m := op.Record(gtx.Ops)
dims = w(gtx)
s.nextCall = m.Stop()
}

if s.offset == 0 {
s.nextCall.Add(gtx.Ops)
return dims
}

offset := smooth(s.offset)

if s.offset > 0 {
defer op.Offset(image.Point{
X: int(float32(dims.Size.X) * (offset - 1)),
}).Push(gtx.Ops).Pop()
s.lastCall.Add(gtx.Ops)

defer op.Offset(image.Point{
X: dims.Size.X,
}).Push(gtx.Ops).Pop()
s.nextCall.Add(gtx.Ops)
} else {
defer op.Offset(image.Point{
X: int(float32(dims.Size.X) * (offset + 1)),
}).Push(gtx.Ops).Pop()
s.lastCall.Add(gtx.Ops)

defer op.Offset(image.Point{
X: -dims.Size.X,
}).Push(gtx.Ops).Pop()
s.nextCall.Add(gtx.Ops)
}
return dims
}

// smooth handles -1 to 1 with ease-in-out cubic easing func.
func smooth(t float32) float32 {
if t < 0 {
return -easeInOutCubic(-t)
}
return easeInOutCubic(t)
}

// easeInOutCubic maps a linear value to a ease-in-out-cubic easing function.
func easeInOutCubic(t float32) float32 {
if t < 0.5 {
return 4 * t * t * t
}
return (t-1)*(2*t-2)*(2*t-2) + 1
}
JustinBeBoy marked this conversation as resolved.
Show resolved Hide resolved
Loading