~f4814n/frost

f03d7b57b352c6704d3b822a117c34996999e572 — Fabian Geiselhart 4 months ago abcd6e7
Split View management into responsive view
2 files changed, 227 insertions(+), 144 deletions(-)

M cmd/frost/main.go
A view/responsive/view.go
M cmd/frost/main.go => cmd/frost/main.go +17 -144
@@ 3,7 3,6 @@ package main
import (
	"context"
	"net/http"
	"time"

	"gioui.org/app"
	"gioui.org/font/gofont"


@@ 15,15 14,11 @@ import (
	"git.sr.ht/~f4814n/frost"
	"git.sr.ht/~f4814n/frost/component/cache"
	"git.sr.ht/~f4814n/frost/platform"
	"git.sr.ht/~f4814n/frost/util"
	"git.sr.ht/~f4814n/frost/view/login"
	"git.sr.ht/~f4814n/frost/view/memberlist"
	"git.sr.ht/~f4814n/frost/view/roomhistory"
	"git.sr.ht/~f4814n/frost/view/roomlist"
	"git.sr.ht/~f4814n/frost/view/responsive"
	"git.sr.ht/~f4814n/matrix"
	"git.sr.ht/~whereswaldon/materials"
	"go.uber.org/zap"
	"golang.org/x/exp/shiny/materialdesign/icons"
)

var theme *material.Theme


@@ 48,11 43,9 @@ type App struct {
	cache    frost.Component
	mux      *frost.Mux

	full, left, middle, right         frost.View
	fullID, leftID, middleID, rightID uint

	modalLayer *materials.ModalLayer
	drawer     *materials.ModalNavDrawer
	view       frost.View
	viewID     uint
}

func newApp() *App {


@@ 82,17 75,6 @@ func newApp() *App {
	}

	a.cache = cache.New(a.cli, platformCache, logger.Named("cache"))
	a.drawer = materials.NewModalNav(a.modalLayer, "Frost", "Here be dragons")
	a.drawer.AddNavItem(materials.NavItem{
		Name: "Rooms",
		Icon: util.MustIcon(icons.SocialGroup),
		Tag:  tagNavRooms,
	})
	a.drawer.AddNavItem(materials.NavItem{
		Name: "Settings",
		Icon: util.MustIcon(icons.ActionSettings),
		Tag:  tagNavSettings,
	})

	return a
}


@@ 155,55 137,8 @@ func (a *App) run() error {
				go a.login(e)
			case frost.InvalidationEvent:
				a.window.Invalidate()
			case frost.ToggleNavigation:
				a.drawer.Appear(time.Now())
			case frost.ShowRoomEvent:
				if a.middle != nil {
					a.middle.Stop()
					a.mux.Unregister(a.middleID)
				}

				self, _ := a.cli.User()
				a.middle = roomhistory.New(self, e.Room, a.modalLayer, theme, a.logger.Named("roomhistory"))

				id, rx, tx := a.mux.Register()
				a.middleID = id
				go a.middle.Run(rx, tx)
			case frost.ShowMemberlistEvent:
				if a.right != nil {
					a.right.Stop()
					a.mux.Unregister(a.rightID)
					a.right = nil
				}

				a.right = memberlist.New(e.Room, a.modalLayer, theme, a.logger.Named("memberlist"))
				id, rx, tx := a.mux.Register()
				a.rightID = id
				go a.right.Run(rx, tx)
			case frost.CloseRoomhistoryEvent:
				if a.right != nil {
					a.right.Stop()
					a.mux.Unregister(a.rightID)
					a.right = nil
				}
				a.middle.Stop()
				a.mux.Unregister(a.middleID)
				a.middle = nil
			case frost.CloseMemberlistEvent:
				a.right.Stop()
				a.mux.Unregister(a.rightID)
				a.right = nil
			case frost.BackEvent:
				if a.right != nil {
					a.right.Stop()
					a.mux.Unregister(a.rightID)
					a.right = nil
				} else if a.middle != nil {
					a.middle.Stop()
					a.mux.Unregister(a.middleID)
					a.middle = nil
				}
			}

		case <-matrixEvents:
			a.window.Invalidate()
		}


@@ 211,57 146,7 @@ func (a *App) run() error {
}

func (a *App) Layout(gtx Gtx) Dims {
	leftsize := gtx.Px(unit.Dp(400))
	rightsize := gtx.Px(unit.Dp(400))

	if a.full != nil {
		return a.full.Layout(gtx)
	}

	if a.left == nil && a.middle == nil && a.right == nil {
		return NoView{}.Layout(gtx)
	}

	if gtx.Constraints.Max.X < gtx.Px(unit.Dp(720)) {
		if view := a.focused(); view != nil {
			return view.Layout(gtx)
		}
		return NoView{}.Layout(gtx)
	} else if gtx.Constraints.Max.X < gtx.Px(unit.Dp(1100)) {
		return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
			layout.Rigid(func(gtx Gtx) Dims {
				gtx.Constraints.Max.X = leftsize
				return a.left.Layout(gtx)
			}),
			layout.Flexed(1, func(gtx Gtx) Dims {
				if view := a.focused(); view != nil && view != a.left {
					return view.Layout(gtx)
				} else {
					return NoView{}.Layout(gtx)
				}
			}),
		)
	} else {
		return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
			layout.Rigid(func(gtx Gtx) Dims {
				gtx.Constraints.Max.X = leftsize
				return a.left.Layout(gtx)
			}),
			layout.Flexed(1, func(gtx Gtx) Dims {
				if a.middle != nil {
					return a.middle.Layout(gtx)
				}
				return NoView{}.Layout(gtx)
			}),
			layout.Rigid(func(gtx Gtx) Dims {
				if a.right != nil {
					gtx.Constraints.Max.X = rightsize
					return a.right.Layout(gtx)
				}
				return Dims{}
			}),
		)
	}
	return a.view.Layout(gtx)
}

func (a *App) login(event login.StartEvent) {


@@ 282,14 167,14 @@ func (a *App) login(event login.StartEvent) {
		a.logger.Debug("Failed to flush config", zap.Error(err))
	}

	a.full.Stop()
	a.mux.Unregister(a.fullID)
	a.full = nil
	a.view.Stop()
	a.mux.Unregister(a.viewID)
	a.view = nil

	a.left = roomlist.New(a.cli, a.modalLayer, theme, a.logger.Named("roomlist"))
	a.view = responsive.New(a.cli, a.mux, theme, a.logger.Named("roomlist"))
	id, rx, tx := a.mux.Register()
	a.leftID = id
	go a.left.Run(rx, tx)
	a.viewID = id
	go a.view.Run(rx, tx)

	matrixEvents := make(chan matrix.Event, 1000)
	a.cli.Notify(matrixEvents)


@@ 297,18 182,6 @@ func (a *App) login(event login.StartEvent) {
	a.sync()
}

func (a *App) focused() frost.View {
	if a.right != nil {
		return a.right
	} else if a.middle != nil {
		return a.middle
	} else if a.left != nil {
		return a.left
	}

	return nil
}

func (a *App) initialView() {
	if a.config.Session != nil {
		err := a.cli.LoadToken(a.config.Session.MxID, a.config.Session.DeviceID)


@@ 317,15 190,15 @@ func (a *App) initialView() {
		}
		a.sync()

		a.left = roomlist.New(a.cli, a.modalLayer, theme, a.logger.Named("roomlist"))
		a.view = responsive.New(a.cli, a.mux, theme, a.logger.Named("responsive"))
		id, rx, tx := a.mux.Register()
		a.leftID = id
		go a.left.Run(rx, tx)
		a.viewID = id
		go a.view.Run(rx, tx)
	} else {
		a.full = login.New(theme, a.logger.Named("login"))
		a.view = login.New(theme, a.logger.Named("login"))
		id, rx, tx := a.mux.Register()
		a.fullID = id
		go a.full.Run(rx, tx)
		a.viewID = id
		go a.view.Run(rx, tx)
	}

	a.rx <- frost.InvalidationEvent{}

A view/responsive/view.go => view/responsive/view.go +210 -0
@@ 0,0 1,210 @@
package responsive

import (
	"sync"
	"time"

	"git.sr.ht/~f4814n/frost/view/memberlist"
	"git.sr.ht/~f4814n/frost/view/roomhistory"
	"git.sr.ht/~f4814n/frost/view/roomlist"

	"gioui.org/layout"
	"gioui.org/unit"
	"gioui.org/widget/material"
	"git.sr.ht/~f4814n/frost"
	"git.sr.ht/~f4814n/frost/util"
	"git.sr.ht/~f4814n/matrix"
	"git.sr.ht/~whereswaldon/materials"
	"go.uber.org/zap"
	"golang.org/x/exp/shiny/materialdesign/icons"
)

type (
	g = layout.Context
	d = layout.Dimensions
)

const (
	left   = 0
	middle = 1
	right  = 2
)

// Tags used for the NavDrawer
const (
	tagNavSettings byte = iota
	tagNavRooms
)

type view struct {
	views      []idView
	modalLayer *materials.ModalLayer
	drawer     *materials.ModalNavDrawer
	theme      *material.Theme
	logger     *zap.Logger
	cli        *matrix.Client
	mut        sync.Mutex
	mux        *frost.Mux
}

type idView struct {
	frost.View
	ID uint
}

func New(cli *matrix.Client, mux *frost.Mux, theme *material.Theme, logger *zap.Logger) frost.View {
	view := &view{
		logger:     logger,
		modalLayer: materials.NewModal(),
		theme:      theme,
		views:      make([]idView, 1),
		mux:        mux,
		cli:        cli,
	}

	// Setup Drawer
	view.drawer = materials.NewModalNav(view.modalLayer, "Frost", "Here be dragons")

	view.drawer.AddNavItem(materials.NavItem{
		Name: "Rooms",
		Icon: util.MustIcon(icons.SocialGroup),
		Tag:  tagNavRooms,
	})

	view.drawer.AddNavItem(materials.NavItem{
		Name: "Settings",
		Icon: util.MustIcon(icons.ActionSettings),
		Tag:  tagNavSettings,
	})

	// Add roomlist as the leftmost view
	id, rx, tx := view.mux.Register()
	view.views[left] = idView{roomlist.New(cli, view.modalLayer, theme, view.logger.Named("roomlist")), id}
	go view.views[left].Run(rx, tx)

	return view
}

func (v *view) view(i int) (idView, bool) {
	if len(v.views) >= i+1 {
		return v.views[i], true
	}
	return idView{}, false
}

func (v *view) closeView(location int) {
	if len(v.views) > location {
		v.views[location].Stop()
		v.mux.Unregister(v.views[location].ID)
		v.views = v.views[:len(v.views)-1]
	}
}

func (v *view) addView(view idView, location int) {
	if view, ok := v.view(location); ok {
		view.Stop()
		v.mux.Unregister(view.ID)
	}

	if len(v.views) <= location {
		v.views = append(v.views, view)
	} else {
		v.views[location] = view
	}
}

func (v *view) Run(rx chan frost.Event, tx chan frost.Event) {
	for event := range rx {
		switch e := event.(type) {
		case frost.ToggleNavigation:
			v.drawer.Appear(time.Now())
		case frost.ShowRoomEvent:
			self, _ := v.cli.User()
			id, rx, tx := v.mux.Register()
			view := idView{
				roomhistory.New(
					self, e.Room, v.modalLayer, v.theme, v.logger.Named("roomhistory"),
				),
				id,
			}
			v.addView(view, middle)
			go view.Run(rx, tx)
		case frost.ShowMemberlistEvent:
			id, rx, tx := v.mux.Register()
			view := idView{
				memberlist.New(e.Room, v.modalLayer, v.theme, v.logger.Named("memberlist")),
				id,
			}
			v.addView(view, right)
			go view.Run(rx, tx)
		case frost.CloseRoomhistoryEvent:
			v.closeView(middle)
		case frost.CloseMemberlistEvent:
			v.closeView(right)
		case frost.BackEvent:
			v.closeView(len(v.views) - 1)
		}
	}
}

func (v *view) focused() frost.View {
	return v.views[len(v.views)-1]
}

func (v *view) Layout(gtx layout.Context) layout.Dimensions {
	v.mut.Lock()
	defer v.mut.Unlock()

	switch {
	case gtx.Constraints.Max.X < gtx.Px(unit.Dp(720)):
		return v.layoutSmall(gtx)
	case gtx.Constraints.Max.X < gtx.Px(unit.Dp(1100)):
		return v.layoutMedium(gtx)
	default:
		return v.layoutLarge(gtx)
	}
}

func (v *view) layoutSmall(gtx g) d {
	return v.focused().Layout(gtx)
}

func (v *view) layoutMedium(gtx g) d {
	children := []layout.FlexChild{
		layout.Rigid(func(gtx g) d {
			gtx.Constraints.Max.X = gtx.Px(unit.Dp(400))
			return v.views[left].Layout(gtx)
		}),
	}

	if len(v.views) >= 2 {
		children = append(children, layout.Flexed(1, v.focused().Layout))
	}

	return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, children...)
}

func (v *view) layoutLarge(gtx g) d {
	children := []layout.FlexChild{
		layout.Rigid(func(gtx g) d {
			gtx.Constraints.Max.X = gtx.Px(unit.Dp(400))
			return v.views[left].Layout(gtx)
		}),
	}

	if len(v.views) >= 2 {
		children = append(children, layout.Flexed(1, v.views[middle].Layout))
	}

	if len(v.views) >= 3 {
		children = append(children, layout.Rigid(func(gtx g) d {
			gtx.Constraints.Max.X = gtx.Px(unit.Dp(400))
			return v.focused().Layout(gtx)
		}))
	}

	return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, children...)
}

func (v *view) Stop() {
}