~whereswaldon/pointstar

9a498c8c33fdaa26d2b93571d1d52ab9b785d432 — Chris Waldon 1 year, 7 months ago 44b11a6
feat: respond to hovering over cards
M client/main.go => client/main.go +2 -2
@@ 7,10 7,10 @@ import (
	"gioui.org/font/gofont"
	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/widget/material"
	"nhooyr.io/websocket"

	"git.sr.ht/~whereswaldon/pointstar/client/log"
	"git.sr.ht/~whereswaldon/pointstar/client/widgets/gameui"
	"git.sr.ht/~whereswaldon/pointstar/gamestate"
	"git.sr.ht/~whereswaldon/pointstar/server/protocol"
)


@@ 84,7 84,7 @@ func eventLoop(w *app.Window) error {
	go ui.Worker.Run(w)

	gofont.Register()
	th := material.NewTheme()
	th := gameui.NewGameTheme()
	skin := &Skin{th}
	gtx := layout.NewContext(w.Queue())
	for {

M client/skin.go => client/skin.go +15 -59
@@ 10,6 10,8 @@ import (
	"gioui.org/unit"
	"gioui.org/widget/material"
	"git.sr.ht/~whereswaldon/pointstar/client/log"
	"git.sr.ht/~whereswaldon/pointstar/client/widgets"
	"git.sr.ht/~whereswaldon/pointstar/client/widgets/gameui"
	"git.sr.ht/~whereswaldon/pointstar/gamestate"
	"git.sr.ht/~whereswaldon/pointstar/server/protocol"
)


@@ 26,7 28,7 @@ var (

// Skin provides methods for rendering the game UI
type Skin struct {
	*material.Theme
	*gameui.Theme
}

func (s *Skin) LayoutAll(g *gamestate.GameState, u *UIState, gtx *layout.Context) {


@@ 184,7 186,7 @@ func (s *Skin) LayoutLobby(g *gamestate.GameState, u *UIState, gtx *layout.Conte

// LayoutHelp displays help information for playing the game
func (s *Skin) LayoutHelp(g *gamestate.GameState, u *UIState, gtx *layout.Context) {
	cardWidth := s.CardWidth(gtx)
	cardWidth := gtx.Px(s.Theme.CardWidth)
	helpWidth := cardWidth * 5
	helpHeight := gtx.Px(unit.Dp(90))
	layout.Center.Layout(gtx, func() {


@@ 204,7 206,7 @@ func (s *Skin) LayoutHelp(g *gamestate.GameState, u *UIState, gtx *layout.Contex
						layout.Flex{}.Layout(gtx,
							layout.Rigid(func() {
								layout.Inset{Bottom: unit.Dp(8)}.Layout(gtx, func() {
									s.LayoutCard(card, gtx)
									s.LayoutCard(card, nil, gtx)
								})
							}),
							layout.Rigid(func() {


@@ 312,7 314,10 @@ func (s *Skin) LayoutPlayed(g *gamestate.GameState, playerNum int, u *UIState, g
		layout.Flexed(1, func() {
			layout.Center.Layout(gtx, func() {
				u.PlayedLists[playerNum].Layout(gtx, len(player.Played), func(index int) {
					s.LayoutCard(player.Played[index], gtx)
					layout.Center.Layout(gtx, func() {
						cardState := &u.PlayedCardState[playerNum][index]
						s.LayoutCardPadded(player.Played[index], cardState, unit.Dp(2), gtx)
					})
				})
			})
		}),


@@ 368,16 373,17 @@ func (s *Skin) LayoutHand(g *gamestate.GameState, u *UIState, gtx *layout.Contex
					layout.Center.Layout(gtx, func() {
						u.HandList.Layout(gtx, len(hand), func(index int) {
							card := hand[index]
							cardState := &u.HandCardState[index]
							button := &u.PlayButtons[index]
							layout.Inset{Bottom: unit.Dp(8)}.Layout(gtx, func() {
								layout.Flex{Axis: layout.Vertical}.Layout(gtx,
									layout.Rigid(func() {
										layout.Inset{Bottom: unit.Dp(8)}.Layout(gtx, func() {
											s.LayoutCard(card, gtx)
											s.LayoutCardPadded(card, cardState, unit.Dp(2), gtx)
										})
									}),
									layout.Rigid(func() {
										cardWidth := s.CardWidth(gtx)
										cardWidth := gtx.Px(s.CardWidth)
										gtx.Constraints.Width.Max = cardWidth
										gtx.Constraints.Width.Min = cardWidth
										layout.Center.Layout(gtx, func() {


@@ 414,58 420,8 @@ func (s *Skin) LayoutHand(g *gamestate.GameState, u *UIState, gtx *layout.Contex
	)
}

func (s *Skin) LayoutCardPadded(c gamestate.Card, gtx *layout.Context) {
	layout.UniformInset(unit.Dp(8)).Layout(gtx, func() {
		s.LayoutCard(c, gtx)
func (s *Skin) LayoutCardPadded(c gamestate.Card, cardState *widgets.Card, padding unit.Value, gtx *layout.Context) {
	layout.UniformInset(padding).Layout(gtx, func() {
		s.LayoutCard(c, cardState, gtx)
	})
}

func (s *Skin) CardWidth(gtx *layout.Context) int {
	return gtx.Px(CardWidth)
}

func (s *Skin) LayoutCard(c gamestate.Card, gtx *layout.Context) {
	cardWidth := s.CardWidth(gtx)
	gtx.Constraints.Width.Max = cardWidth
	var textColor color.RGBA
	switch gamestate.CardTypes[c] {
	case gamestate.Special:
		textColor = SpecialCardTextColor
	case gamestate.Battlefield:
		textColor = BattlefieldCardTextColor
	default:
		textColor = WarshipCardTextColor
	}
	layout.Flex{Axis: layout.Vertical}.Layout(gtx,
		layout.Rigid(func() {
			gtx.Constraints.Width.Max = cardWidth
			gtx.Constraints.Width.Min = cardWidth
			layout.Center.Layout(gtx, func() {
				h := s.Theme.H2(gamestate.CardText[c])
				h.Color = textColor
				h.Layout(gtx)
			})
		}),
		layout.Rigid(func() {
			gtx.Constraints.Width.Max = cardWidth
			gtx.Constraints.Width.Min = cardWidth
			layout.Center.Layout(gtx, func() {
				b := s.Theme.Body2(gamestate.CardNames[c])
				b.TextSize.Scale(.7)
				b.Color = textColor
				b.Layout(gtx)
			})
		}),
	)
}

func (s *Skin) CardListAsFlexChildren(h gamestate.CardList, gtx *layout.Context) []layout.FlexChild {
	var children []layout.FlexChild
	for i := range h {
		card := h[i]
		children = append(children, layout.Rigid(func() {
			s.LayoutCardPadded(card, gtx)
		}))
	}
	return children
}

M client/uistate.go => client/uistate.go +7 -0
@@ 3,6 3,7 @@ package main
import (
	"gioui.org/layout"
	"gioui.org/widget"
	"git.sr.ht/~whereswaldon/pointstar/client/widgets"
	"git.sr.ht/~whereswaldon/pointstar/gamestate"
)



@@ 21,6 22,12 @@ type UIState struct {
	AddAIPlayerButton widget.Button
	PlayerNum         int

	// store the widget state of the player's cards
	HandCardState [gamestate.MaxHandSize]widgets.Card

	// store the state of all played cards
	PlayedCardState [gamestate.MaxPlayers][gamestate.MaxHandSize]widgets.Card

	// whether or not the help screen is being displayed instead of the game interface
	DisplayingHelp bool
	// hold the state of a list of example cards

A client/widgets/card.go => client/widgets/card.go +32 -0
@@ 0,0 1,32 @@
package widgets

import (
	"gioui.org/io/pointer"
	"gioui.org/layout"
)

// Card holds the state for a card widget
type Card struct {
	containsPointer bool
}

// Hovered returns whether this widget was hovered over during the last
// frame
func (c *Card) Hovered(gtx *layout.Context) bool {
	if c == nil {
		return false
	}
	events := gtx.Events(c)
	for _, e := range events {
		switch event := e.(type) {
		case pointer.Event:
			switch event.Type {
			case pointer.Enter:
				c.containsPointer = true
			case pointer.Leave:
				c.containsPointer = false
			}
		}
	}
	return c.containsPointer
}

A client/widgets/gameui/card.go => client/widgets/gameui/card.go +111 -0
@@ 0,0 1,111 @@
package gameui

import (
	"image"
	"image/color"

	"gioui.org/f32"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/unit"
	"gioui.org/widget/material"
	"git.sr.ht/~whereswaldon/pointstar/client/widgets"
	"git.sr.ht/~whereswaldon/pointstar/gamestate"
)

type Theme struct {
	*material.Theme
	SpecialCardTextColor       color.RGBA
	BattlefieldCardTextColor   color.RGBA
	WarshipCardTextColor       color.RGBA
	DefaultCardBackgroundColor color.RGBA
	ActiveCardBackgroundColor  color.RGBA
	CardWidth                  unit.Value
	CardCornerRounding         unit.Value
}

func NewGameTheme() *Theme {
	return &Theme{
		Theme:                      material.NewTheme(),
		WarshipCardTextColor:       color.RGBA{A: 255},
		SpecialCardTextColor:       color.RGBA{R: 155, A: 255},
		BattlefieldCardTextColor:   color.RGBA{R: 148, B: 211, A: 255},
		DefaultCardBackgroundColor: color.RGBA{R: 240, G: 240, B: 240, A: 255},
		ActiveCardBackgroundColor:  color.RGBA{R: 220, G: 220, B: 220, A: 255},

		CardWidth:          unit.Dp(70),
		CardCornerRounding: unit.Dp(8),
	}
}

func (g *Theme) LayoutCard(c gamestate.Card, cardState *widgets.Card, gtx *layout.Context) {
	cardWidth := gtx.Px(g.CardWidth)
	gtx.Constraints.Width.Max = cardWidth
	var textColor color.RGBA
	switch gamestate.CardTypes[c] {
	case gamestate.Special:
		textColor = g.SpecialCardTextColor
	case gamestate.Battlefield:
		textColor = g.BattlefieldCardTextColor
	default:
		textColor = g.WarshipCardTextColor
	}
	macro := op.MacroOp{}
	macro.Record(gtx.Ops)
	layout.Flex{Axis: layout.Vertical}.Layout(gtx,
		layout.Rigid(func() {
			gtx.Constraints.Width.Max = cardWidth
			gtx.Constraints.Width.Min = cardWidth
			layout.Center.Layout(gtx, func() {
				h := g.Theme.H2(gamestate.CardText[c])
				h.Color = textColor
				h.Layout(gtx)
			})
		}),
		layout.Rigid(func() {
			gtx.Constraints.Width.Max = cardWidth
			gtx.Constraints.Width.Min = cardWidth
			layout.Center.Layout(gtx, func() {
				b := g.Theme.Body2(gamestate.CardNames[c])
				b.TextSize.Scale(.7)
				b.Color = textColor
				layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func() {
					b.Layout(gtx)
				})
			})
		}),
	)
	cardHeight := gtx.Dimensions.Size.Y
	macro.Stop()
	gtx.Constraints.Width.Min = cardWidth
	gtx.Constraints.Height.Min = cardHeight
	layout.Stack{}.Layout(gtx,
		layout.Expanded(func() {
			paintOp := paint.ColorOp{Color: g.DefaultCardBackgroundColor}
			if cardState.Hovered(gtx) {
				paintOp.Color = g.ActiveCardBackgroundColor
			}
			paintOp.Add(gtx.Ops)
			cornerRadius := float32(gtx.Px(g.CardCornerRounding))
			bounds := f32.Rectangle{Max: f32.Point{X: float32(cardWidth), Y: float32(cardHeight)}}
			clip.Rect{
				Rect: bounds,
				NE:   cornerRadius,
				NW:   cornerRadius,
				SE:   cornerRadius,
				SW:   cornerRadius,
			}.Op(gtx.Ops).Add(gtx.Ops)
			paint.PaintOp{Rect: bounds}.Add(gtx.Ops)
		}),
		layout.Stacked(func() {
			macro.Add()
		}),
		layout.Stacked(func() {
			pointer.Rect(image.Rect(0, 0, cardWidth, cardHeight)).Add(gtx.Ops)
			pointer.InputOp{Key: cardState}.Add(gtx.Ops)
		}),
	)
}

M gamestate/gamestate.go => gamestate/gamestate.go +3 -0
@@ 61,6 61,9 @@ func (g *GameState) MakeDeck() {
// MaxHandSize is the maximum hand size in the game
const MaxHandSize = 10

// MaxPlayers is the most players the game supports
const MaxPlayers = 6

// SetupWithPlayers initializes all players' hands
func (g *GameState) SetupWithPlayers(players int) {
	g.SetupPlayers(players)

M go.mod => go.mod +5 -2
@@ 1,8 1,11 @@
module git.sr.ht/~whereswaldon/pointstar

go 1.13
go 1.14

require (
	gioui.org v0.0.0-20200401111706-a08674dbcaca
	gioui.org v0.0.0-20200423191319-533bf953f987
	gioui.org/cmd v0.0.0-20200424135930-84d4800a1612 // indirect
	nhooyr.io/websocket v1.8.4
)

replace gioui.org => git.sr.ht/~whereswaldon/gio v0.0.0-20200425212652-6e3f5f1d8965

M go.sum => go.sum +9 -10
@@ 1,22 1,22 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20200401111706-a08674dbcaca h1:3yyshrXY2S0bXVRpzm2agRLzKnuwSDRCedi67WwqalQ=
gioui.org v0.0.0-20200401111706-a08674dbcaca/go.mod h1:AHI9rFr6AEEHCb8EPVtb/p5M+NMJRKH58IOp8O3Je04=
gioui.org/cmd v0.0.0-20200424135930-84d4800a1612 h1:utHFWXNYQuJ6pjLpuUEf/pJKWB8Q/Rx3QJbVCaanqRw=
gioui.org/cmd v0.0.0-20200424135930-84d4800a1612/go.mod h1:KD+OtAQHeyTNNkaXyXyLZz2q8uFiS/e3h3gOp+XzO4Q=
git.sr.ht/~whereswaldon/gio v0.0.0-20200425212652-6e3f5f1d8965 h1:jwITP5+4kfnIszGgSwzX/pGREENTDkUXNhLaL0dnC5w=
git.sr.ht/~whereswaldon/gio v0.0.0-20200425212652-6e3f5f1d8965/go.mod h1:AHI9rFr6AEEHCb8EPVtb/p5M+NMJRKH58IOp8O3Je04=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/compress v1.10.0 h1:92XGj1AcYzA6UrVdd4qIIBrT8OroryvRvdmg/IfmC7Y=
github.com/klauspost/compress v1.10.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=


@@ 34,16 34,15 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e h1:1xWUkZQQ9Z9UuZgNaIR6OQOE7rUFglXUUBZlO+dGg6I=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
nhooyr.io/websocket v1.8.4 h1:P43INlkmY2eCxLvHeiMFK/ROUiOm0NdzRGGDtURbe58=
nhooyr.io/websocket v1.8.4/go.mod h1:LiqdCg1Cu7TPWxEvPjPa0TGYxCsy4pHNTN9gGluwBpQ=