Skip to content

Commit 7fc2a4b

Browse files
authored
tweak: [auth] improve buttons (#96)
1 parent 5bad9f3 commit 7fc2a4b

File tree

4 files changed

+144
-79
lines changed

4 files changed

+144
-79
lines changed

auth/gioauth/authlayout/apple.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ import (
55
"gioui.org/layout"
66
"gioui.org/unit"
77
"github.com/gioui-plugins/gio-plugins/auth/gioauth/authlayout/internal"
8-
"github.com/inkeliz/giosvg"
98
"image/color"
109
)
1110

1211
// DefaultLightAppleButtonStyle is the default style for Apple buttons.
1312
var DefaultLightAppleButtonStyle = ButtonStyle{
14-
Text: "Continue with Apple",
1513
TextSize: unit.Dp(16),
1614
TextFont: font.Font{},
1715
TextShaper: internal.ShaperGoogleRoboto,
@@ -20,12 +18,13 @@ var DefaultLightAppleButtonStyle = ButtonStyle{
2018
IconAlignment: layout.Start,
2119
BackgroundColor: color.NRGBA{R: 0, G: 0, B: 0, A: 255},
2220
IconColor: color.NRGBA{R: 255, G: 255, B: 255, A: 255},
21+
IconVector: internal.VectorAppleLogo,
22+
IconPadding: 24,
2323
Format: FormatRounded,
2424
}
2525

2626
// DefaultDarkAppleButtonStyle is the default style for Apple buttons.
2727
var DefaultDarkAppleButtonStyle = ButtonStyle{
28-
Text: "Continue with Apple",
2928
TextSize: unit.Dp(16),
3029
TextFont: font.Font{},
3130
TextShaper: internal.ShaperGoogleRoboto,
@@ -34,26 +33,27 @@ var DefaultDarkAppleButtonStyle = ButtonStyle{
3433
IconAlignment: layout.Start,
3534
BackgroundColor: color.NRGBA{R: 255, G: 255, B: 255, A: 255},
3635
IconColor: color.NRGBA{A: 255},
36+
IconVector: internal.VectorAppleLogo,
37+
IconPadding: 24,
3738
Format: FormatRounded,
3839
}
3940

40-
// AppleDummyButton is a button that can be used to sign in with Apple.
41-
// It doesn't perform any action, it just displays a button.
42-
type AppleDummyButton struct {
43-
ButtonStyle
44-
Pointer
45-
icon *giosvg.Icon
41+
// DefaultAppleTextContinue is the default text for Google buttons.
42+
var DefaultAppleTextContinue = [2]string{
43+
"Continue with Apple",
44+
"Sign in",
4645
}
4746

48-
// Layout lays out the button, with the default text (from ButtonStyle).
49-
func (g *AppleDummyButton) Layout(gtx layout.Context) layout.Dimensions {
50-
return g.LayoutText(gtx, g.Text)
47+
func NewAppleButton() *Button {
48+
return &Button{
49+
ButtonStyle: DefaultLightAppleButtonStyle,
50+
ButtonTexts: DefaultAppleTextContinue,
51+
}
5152
}
5253

53-
// LayoutText lays out the button with the given text.
54-
func (g *AppleDummyButton) LayoutText(gtx layout.Context, text string) layout.Dimensions {
55-
if g.icon == nil {
56-
g.icon = giosvg.NewIcon(internal.VectorAppleLogo)
54+
func NewAppleButtonDark() *Button {
55+
return &Button{
56+
ButtonStyle: DefaultDarkAppleButtonStyle,
57+
ButtonTexts: DefaultAppleTextContinue,
5758
}
58-
return g.layoutText(gtx, g.icon, &g.Pointer, text, 0, gtx.Dp(24))
5959
}

auth/gioauth/authlayout/button.go

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,56 @@ const (
2626
FormatPill
2727
)
2828

29+
// ButtonTexts is a list of texts for the button.
30+
// The first is preferred, the second is fallback,
31+
// when the first doesn't fit the button width.
32+
type ButtonTexts [2]string
33+
2934
// ButtonStyle is the style for Google buttons.
3035
//
3136
// Notice that you may violate some branding guidelines, it's
3237
// safer to use DefaultGoogleButtonStyle or DefaultAppleButtonStyle.
3338
type ButtonStyle struct {
34-
Text string
35-
TextSize unit.Dp
36-
TextFont font.Font
37-
TextShaper *text.Shaper
38-
TextColor color.NRGBA
39-
TextAlignment layout.Alignment
40-
IconAlignment layout.Alignment
41-
IconColor color.NRGBA
42-
BackgroundColor color.NRGBA
39+
// TextSize is the size of the text.
40+
TextSize unit.Dp
41+
// TextFont is the font to use for the text.
42+
TextFont font.Font
43+
// TextShaper is the shaper to use for the text.
44+
TextShaper *text.Shaper
45+
// TextColor is the color of the text.
46+
TextColor color.NRGBA
47+
// TextAlignment is the alignment of the text.
48+
TextAlignment layout.Alignment
49+
// IconAlignment is the alignment of the icon.
50+
IconAlignment layout.Alignment
51+
// IconColor is the color of the icon.
52+
IconColor color.NRGBA
53+
// IconSize is the size of the icon.
54+
IconSize unit.Dp
55+
// IconPadding is the padding of the icon.
56+
IconPadding unit.Dp
57+
// IconVector is the vector of the icon.
58+
IconVector giosvg.Vector
59+
// BackgroundColor is the color of the background.
60+
BackgroundColor color.NRGBA
61+
// BackgroundIconColor is the color of the background of the icon.
4362
BackgroundIconColor color.NRGBA
44-
BorderColor color.NRGBA
45-
BorderThickness unit.Dp
46-
Format Format
63+
// BorderColor is the color of the border.
64+
BorderColor color.NRGBA
65+
// BorderThickness is the thickness of the border.
66+
BorderThickness unit.Dp
67+
// Format is the format of the button.
68+
Format Format
69+
70+
icon *giosvg.Icon
4771
}
4872

49-
func (b ButtonStyle) label(gtx layout.Context, text string) (op.CallOp, layout.Dimensions) {
73+
func (b *ButtonStyle) label(gtx layout.Context, text string) (call op.CallOp, dims layout.Dimensions) {
5074
gtx.Constraints.Min.X = 0
5175
gtx.Constraints.Min.Y = 0
5276

5377
r := op.Record(gtx.Ops)
54-
dims := widget.Label{}.Layout(gtx, b.TextShaper, b.TextFont, gtx.Metric.DpToSp(b.TextSize), text, toLabelColor(gtx, b.TextColor))
78+
dims = widget.Label{}.Layout(gtx, b.TextShaper, b.TextFont, gtx.Metric.DpToSp(b.TextSize), text, toLabelColor(gtx, b.TextColor))
5579
return r.Stop(), dims
5680
}
5781

@@ -61,8 +85,16 @@ func toLabelColor(gtx layout.Context, c color.NRGBA) op.CallOp {
6185
return r.Stop()
6286
}
6387

64-
func (b ButtonStyle) layoutText(gtx layout.Context, icon *giosvg.Icon, pointer *Pointer, text string, logoSize int, logoPadding int) layout.Dimensions {
65-
label, labelDims := b.label(gtx, text)
88+
func (b *ButtonStyle) LayoutText(gtx layout.Context, pointer *Pointer, texts ButtonTexts) layout.Dimensions {
89+
return b.layoutText(gtx, pointer, texts, 0)
90+
}
91+
92+
func (b *ButtonStyle) layoutText(gtx layout.Context, pointer *Pointer, texts ButtonTexts, textIndex int) layout.Dimensions {
93+
if b.icon == nil {
94+
b.icon = giosvg.NewIcon(b.IconVector)
95+
}
96+
97+
label, labelDims := b.label(gtx, texts[textIndex])
6698

6799
minHeight := int(math.Round(float64(labelDims.Size.Y) * 233 / 100))
68100

@@ -73,68 +105,82 @@ func (b ButtonStyle) layoutText(gtx layout.Context, icon *giosvg.Icon, pointer *
73105
Right: unit.Dp(16),
74106
}
75107

76-
if logoSize == 0 {
77-
logoSize = labelDims.Size.Y
108+
iconSize := gtx.Dp(b.IconSize)
109+
iconPadding := gtx.Dp(b.IconPadding)
110+
111+
if b.IconSize == 0 {
112+
iconSize = labelDims.Size.Y
78113
}
79114

80-
avalSize := gtx.Constraints.Max.X - (labelDims.Size.X + gtx.Dp(inset.Left) + gtx.Dp(inset.Right) + logoSize)
81-
if avalSize < 0 {
82-
// The label is too long, we need to render the icon-only button.
115+
avalSize := gtx.Constraints.Max.X - (labelDims.Size.X + gtx.Dp(inset.Left) + gtx.Dp(inset.Right) + iconSize + iconPadding)
116+
isLogoOnly := avalSize < 0
117+
if avalSize > iconPadding && b.TextAlignment != layout.Start && b.IconAlignment != layout.Middle {
118+
iconPadding = 0
83119
}
84120

85-
if avalSize > (logoPadding*2) && b.TextAlignment != layout.Start && b.IconAlignment != layout.Middle {
86-
logoPadding = 0
121+
if isLogoOnly && textIndex != len(texts)-1 {
122+
return b.layoutText(gtx, pointer, texts, textIndex+1)
123+
}
124+
125+
if isLogoOnly {
126+
labelDims.Size.X = 0
127+
iconPadding = 0
87128
}
88129

89130
main := op.Record(gtx.Ops)
90131
dims := inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
91132
d := layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, labelDims.Size.Y)}
92133

93134
{
135+
align := b.IconAlignment
136+
if isLogoOnly {
137+
align = layout.Middle
138+
}
139+
94140
// Logo
95141
var off op.TransformStack
96-
switch b.IconAlignment {
142+
switch align {
97143
case layout.Start, layout.Baseline:
98144
off = op.Offset(image.Pt(0, 0)).Push(gtx.Ops)
99145
case layout.Middle:
100-
off = op.Offset(image.Pt((gtx.Constraints.Max.X-logoSize-logoPadding-labelDims.Size.X)/2, 0)).Push(gtx.Ops)
146+
off = op.Offset(image.Pt((gtx.Constraints.Max.X-iconSize-iconPadding-labelDims.Size.X)/2, 0)).Push(gtx.Ops)
101147
case layout.End:
102-
off = op.Offset(image.Pt(gtx.Constraints.Max.X-logoSize, 0)).Push(gtx.Ops)
148+
off = op.Offset(image.Pt(gtx.Constraints.Max.X-iconSize, 0)).Push(gtx.Ops)
103149
}
104150

105151
// Logo Background
106152
padding := gtx.Dp(6)
107153
offBackground := op.Offset(image.Pt(-padding/2, -padding/2)).Push(gtx.Ops)
108-
background := clip.UniformRRect(image.Rectangle{Max: image.Pt(logoSize+padding, logoSize+padding)}, (logoSize+padding)/2).Push(gtx.Ops)
154+
background := clip.UniformRRect(image.Rectangle{Max: image.Pt(iconSize+padding, iconSize+padding)}, (iconSize+padding)/2).Push(gtx.Ops)
109155
paint.Fill(gtx.Ops, b.BackgroundIconColor)
110156
background.Pop()
111157
offBackground.Pop()
112158

113159
gtx := gtx
114160
gtx.Constraints.Min = image.Point{}
115-
gtx.Constraints.Max.X, gtx.Constraints.Max.Y = logoSize, logoSize
161+
gtx.Constraints.Max.X, gtx.Constraints.Max.Y = iconSize, iconSize
116162
if b.IconColor.A != 0 {
117163
paint.ColorOp{Color: b.IconColor}.Add(gtx.Ops)
118164
}
119165

120166
iconR := op.Record(gtx.Ops)
121-
dimsIcon := icon.Layout(gtx)
167+
dimsIcon := b.icon.Layout(gtx)
122168
iconOp := iconR.Stop()
123169

124-
iconOff := op.Offset(image.Pt((logoSize-dimsIcon.Size.X)/2, (logoSize-dimsIcon.Size.Y)/2)).Push(gtx.Ops)
170+
iconOff := op.Offset(image.Pt((iconSize-dimsIcon.Size.X)/2, (iconSize-dimsIcon.Size.Y)/2)).Push(gtx.Ops)
125171
iconOp.Add(gtx.Ops)
126172
iconOff.Pop()
127173

128174
off.Pop()
129175
}
130176

131-
{
177+
if !isLogoOnly {
132178
// Text
133179
gtx := gtx
134-
gtx.Constraints.Max.X = gtx.Constraints.Max.X - logoSize - logoPadding
180+
gtx.Constraints.Max.X = gtx.Constraints.Max.X - iconSize - iconPadding
135181

136182
if b.TextAlignment != layout.End {
137-
defer op.Offset(image.Pt(logoSize+logoPadding, 0)).Push(gtx.Ops).Pop()
183+
defer op.Offset(image.Pt(iconSize+iconPadding, 0)).Push(gtx.Ops).Pop()
138184
}
139185

140186
switch b.TextAlignment {

auth/gioauth/authlayout/generic.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package authlayout
2+
3+
import (
4+
"gioui.org/layout"
5+
"github.com/gioui-plugins/gio-plugins/auth/gioauth/authlayout/internal"
6+
)
7+
8+
var (
9+
// ShaperGoogleRoboto is the default shaper for Google and Apple buttons,
10+
// it's exposed for external use, so you can use it in your own
11+
// widgets.
12+
ShaperGoogleRoboto = internal.ShaperGoogleRoboto
13+
)
14+
15+
// Button is widget that display a button with a text and an icon,
16+
// and can be clicked, you can get the click event using the Pointer.Clicked method.
17+
type Button struct {
18+
ButtonStyle
19+
ButtonTexts
20+
Pointer
21+
}
22+
23+
// Layout lays out the button, with the default text (from ButtonStyle).
24+
func (g *Button) Layout(gtx layout.Context) layout.Dimensions {
25+
return g.LayoutText(gtx, g.ButtonTexts)
26+
}
27+
28+
// LayoutText lays out the button with the given text.
29+
func (g *Button) LayoutText(gtx layout.Context, texts ButtonTexts) layout.Dimensions {
30+
return g.ButtonStyle.LayoutText(gtx, &g.Pointer, texts)
31+
}

auth/gioauth/authlayout/google.go

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,25 @@ import (
55
"gioui.org/layout"
66
"gioui.org/unit"
77
"github.com/gioui-plugins/gio-plugins/auth/gioauth/authlayout/internal"
8-
"github.com/inkeliz/giosvg"
98
"image/color"
109
)
1110

1211
// DefaultLightGoogleButtonStyle is the default style for Google buttons.
1312
var DefaultLightGoogleButtonStyle = ButtonStyle{
14-
Text: "Continue with Google",
1513
TextSize: unit.Dp(16),
1614
TextFont: font.Font{},
1715
TextShaper: internal.ShaperGoogleRoboto,
1816
TextColor: color.NRGBA{R: 60, G: 64, B: 67, A: 255},
1917
TextAlignment: layout.Middle,
2018
IconAlignment: layout.Start,
2119
BackgroundColor: color.NRGBA{R: 255, G: 255, B: 255, A: 255},
20+
IconVector: internal.VectorGoogleLogo,
21+
IconPadding: 24,
2222
Format: FormatRounded,
2323
}
2424

2525
// DefaultDarkGoogleButtonStyle is the default style for Google buttons.
2626
var DefaultDarkGoogleButtonStyle = ButtonStyle{
27-
Text: "Continue with Google",
2827
TextSize: unit.Dp(16),
2928
TextFont: font.Font{},
3029
TextShaper: internal.ShaperGoogleRoboto,
@@ -33,38 +32,27 @@ var DefaultDarkGoogleButtonStyle = ButtonStyle{
3332
IconAlignment: layout.Start,
3433
BackgroundColor: color.NRGBA{R: 66, G: 133, B: 244, A: 255},
3534
BackgroundIconColor: color.NRGBA{R: 255, G: 255, B: 255, A: 255},
35+
IconVector: internal.VectorGoogleLogo,
36+
IconPadding: 24,
3637
Format: FormatRounded,
3738
}
3839

39-
// GoogleDummyButton is a button that can be used to sign in with Google.
40-
// It doesn't perform any action, it just displays a button.
41-
//
42-
// You need to call Clicked (from ButtonStyle) to check if the button was
43-
// clicked, and Layout to lay out the button.
44-
//
45-
// Usually you would do something similar to:
46-
//
47-
// if googleButton.Clicked(gtx) {
48-
// // Perform the Google sign in.
49-
// gtx.Execute(gioauth.Open{Tag: tag, Provider: google.IdentifierGoogle, Nonce: nonce})
50-
// }
51-
//
52-
// googleButton.Layout(gtx)
53-
type GoogleDummyButton struct {
54-
ButtonStyle
55-
Pointer
56-
icon *giosvg.Icon
40+
// DefaultGoogleTextContinue is the default text for Google buttons.
41+
var DefaultGoogleTextContinue = [2]string{
42+
"Continue with Google",
43+
"Sign in",
5744
}
5845

59-
// Layout lays out the button, with the default text (from ButtonStyle).
60-
func (g *GoogleDummyButton) Layout(gtx layout.Context) layout.Dimensions {
61-
return g.LayoutText(gtx, g.Text)
46+
func NewGoogleButton() *Button {
47+
return &Button{
48+
ButtonStyle: DefaultLightGoogleButtonStyle,
49+
ButtonTexts: DefaultGoogleTextContinue,
50+
}
6251
}
6352

64-
// LayoutText lays out the button with the given text.
65-
func (g *GoogleDummyButton) LayoutText(gtx layout.Context, text string) layout.Dimensions {
66-
if g.icon == nil {
67-
g.icon = giosvg.NewIcon(internal.VectorGoogleLogo)
53+
func NewGoogleButtonDark() *Button {
54+
return &Button{
55+
ButtonStyle: DefaultDarkGoogleButtonStyle,
56+
ButtonTexts: DefaultGoogleTextContinue,
6857
}
69-
return g.layoutText(gtx, g.icon, &g.Pointer, text, 0, gtx.Dp(24))
7058
}

0 commit comments

Comments
 (0)