Update chat and game status color scheme
This commit is contained in:
parent
22a97cdbfe
commit
67cb241e1e
7 changed files with 140 additions and 78 deletions
|
@ -20,6 +20,8 @@ type board struct {
|
|||
x, y int
|
||||
w, h int
|
||||
|
||||
fullHeight bool
|
||||
|
||||
innerW, innerH int
|
||||
|
||||
op *ebiten.DrawImageOptions
|
||||
|
@ -215,18 +217,14 @@ func (b *board) updateBackgroundImage() {
|
|||
borderImage := image.NewRGBA(image.Rect(0, 0, b.w, b.h))
|
||||
gc = draw2dimg.NewGraphicContext(borderImage)
|
||||
gc.SetStrokeColor(borderColor)
|
||||
// - Outside left
|
||||
gc.SetLineWidth(2)
|
||||
gc.MoveTo(float64(1), float64(0))
|
||||
gc.LineTo(float64(1), float64(b.h))
|
||||
// - Center
|
||||
gc.SetLineWidth(2)
|
||||
gc.MoveTo(float64(frameW/2), float64(0))
|
||||
gc.LineTo(float64(frameW/2), float64(b.h))
|
||||
gc.Stroke()
|
||||
// - Outside right
|
||||
gc.MoveTo(float64(frameW), float64(0))
|
||||
gc.LineTo(float64(frameW), float64(b.h))
|
||||
gc.Close()
|
||||
gc.Stroke()
|
||||
// - Inside left
|
||||
gc.SetLineWidth(1)
|
||||
|
@ -248,6 +246,19 @@ func (b *board) updateBackgroundImage() {
|
|||
gc.LineTo(float64(edgeStart), float64(b.verticalBorderSize))
|
||||
gc.Close()
|
||||
gc.Stroke()
|
||||
if !b.fullHeight {
|
||||
// - Outside left
|
||||
gc.SetLineWidth(1)
|
||||
gc.MoveTo(float64(0), float64(0))
|
||||
gc.LineTo(float64(0), float64(b.h))
|
||||
// Top
|
||||
gc.MoveTo(0, float64(0))
|
||||
gc.LineTo(float64(b.w), float64(0))
|
||||
// Bottom
|
||||
gc.MoveTo(0, float64(b.h))
|
||||
gc.LineTo(float64(b.w), float64(b.h))
|
||||
gc.Stroke()
|
||||
}
|
||||
img = ebiten.NewImageFromImage(borderImage)
|
||||
b.op.GeoM.Reset()
|
||||
b.op.GeoM.Translate(b.horizontalBorderSize-borderSize, 0)
|
||||
|
@ -918,12 +929,26 @@ func (b *board) movePiece(from int, to int) {
|
|||
}
|
||||
}
|
||||
|
||||
// WatchingGame returns whether the active game is being watched.
|
||||
func (b *board) watchingGame() bool {
|
||||
return !b.playingGame() && b.s[fibs.StatePlayerName] != "" && b.s[fibs.StateOpponentName] != ""
|
||||
}
|
||||
|
||||
// PlayingGame returns whether the active game is being played.
|
||||
func (b *board) playingGame() bool {
|
||||
return b.s[fibs.StatePlayerName] == "You" || b.s[fibs.StateOpponentName] == "You"
|
||||
}
|
||||
|
||||
func (b *board) playerTurn() bool {
|
||||
return b.playingGame() && b.v[fibs.StateTurn] == b.v[fibs.StatePlayerColor]
|
||||
}
|
||||
|
||||
func (b *board) update() {
|
||||
if b.Client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if b.dragging == nil {
|
||||
if b.dragging == nil && b.playerTurn() {
|
||||
// TODO allow grabbing multiple pieces by grabbing further down the stack
|
||||
|
||||
handleReset := func(x, y int) bool {
|
||||
|
|
|
@ -3,8 +3,8 @@ package game
|
|||
import "image/color"
|
||||
|
||||
var (
|
||||
tableColor = color.RGBA{0, 0, 0, 255}
|
||||
//tableColor = color.RGBA{0, 102, 51, 255}
|
||||
//tableColor = color.RGBA{0, 102, 51, 255}
|
||||
tableColor = color.RGBA{65, 40, 14, 255}
|
||||
frameColor = color.RGBA{65, 40, 14, 255}
|
||||
borderColor = color.RGBA{0, 0, 0, 255}
|
||||
faceColor = color.RGBA{120, 63, 25, 255}
|
||||
|
|
60
game/game.go
60
game/game.go
|
@ -45,7 +45,7 @@ var (
|
|||
|
||||
var (
|
||||
lightCheckerColor = color.RGBA{232, 211, 162, 255}
|
||||
darkCheckerColor = color.RGBA{51, 0, 111, 255}
|
||||
darkCheckerColor = color.RGBA{0, 0, 0, 255}
|
||||
)
|
||||
|
||||
const defaultServerAddress = "fibs.com:4321"
|
||||
|
@ -54,6 +54,15 @@ const maxStatusWidthRatio = 0.5
|
|||
|
||||
const bufferCharacterWidth = 54
|
||||
|
||||
const lobbyCharacterWidth = 48
|
||||
|
||||
const showGameBufferLines = 4
|
||||
|
||||
const (
|
||||
minWidth = 320
|
||||
minHeight = 240
|
||||
)
|
||||
|
||||
func init() {
|
||||
loadAssets(0)
|
||||
|
||||
|
@ -461,13 +470,13 @@ http://www.fibs.com/help.html#register`
|
|||
return
|
||||
}
|
||||
|
||||
g.gameBuffer.draw(screen)
|
||||
g.statusBuffer.draw(screen)
|
||||
if !viewBoard {
|
||||
// Lobby screen
|
||||
g.lobby.draw(screen)
|
||||
} else {
|
||||
// Game board screen
|
||||
g.gameBuffer.draw(screen)
|
||||
g.statusBuffer.draw(screen)
|
||||
g.Board.draw(screen)
|
||||
}
|
||||
|
||||
|
@ -506,6 +515,12 @@ http://www.fibs.com/help.html#register`
|
|||
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
s := ebiten.DeviceScaleFactor()
|
||||
outsideWidth, outsideHeight = int(float64(outsideWidth)*s), int(float64(outsideHeight)*s)
|
||||
if outsideWidth < minWidth {
|
||||
outsideWidth = minWidth
|
||||
}
|
||||
if outsideHeight < minHeight {
|
||||
outsideHeight = minHeight
|
||||
}
|
||||
if g.screenW == outsideWidth && g.screenH == outsideHeight {
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
@ -517,12 +532,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||
statusBufferWidth = int(float64(g.screenW) * maxStatusWidthRatio)
|
||||
}
|
||||
|
||||
showGameBufferLines := 8
|
||||
gameBufferHeight := g.statusBuffer.chatFontSize * showGameBufferLines * 2
|
||||
statusBufferHeight := g.screenH - gameBufferHeight
|
||||
|
||||
g.lobby.setRect(0, 0, g.screenW, g.screenH)
|
||||
|
||||
g.Board.fullHeight = true
|
||||
g.Board.setRect(0, 0, g.screenW-statusBufferWidth, g.screenH)
|
||||
|
||||
availableWidth := g.screenW - (g.Board.innerW + int(g.Board.horizontalBorderSize*2))
|
||||
|
@ -532,15 +542,43 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||
}
|
||||
|
||||
if g.Board.h > g.Board.w {
|
||||
g.Board.fullHeight = false
|
||||
g.Board.setRect(0, 0, g.Board.w, g.Board.w)
|
||||
}
|
||||
|
||||
if g.screenW > 200 {
|
||||
g.statusBuffer.padding = 2
|
||||
g.gameBuffer.padding = 2
|
||||
} else if g.screenW > 100 {
|
||||
g.statusBuffer.padding = 1
|
||||
g.gameBuffer.padding = 1
|
||||
} else {
|
||||
g.statusBuffer.padding = 0
|
||||
g.gameBuffer.padding = 0
|
||||
}
|
||||
|
||||
bufferPadding := int(g.Board.horizontalBorderSize / 2)
|
||||
|
||||
gameBufferHeight := (g.gameBuffer.chatLineHeight * showGameBufferLines) + (g.gameBuffer.padding * 4)
|
||||
|
||||
g.lobby.buttonBarHeight = gameBufferHeight + int(float64(bufferPadding)*1.5)
|
||||
minLobbyWidth := text.BoundString(mediumFont, strings.Repeat("A", lobbyCharacterWidth)).Dx()
|
||||
if g.Board.w >= minLobbyWidth {
|
||||
g.lobby.fullscreen = false
|
||||
g.lobby.setRect(0, 0, g.Board.w, g.screenH)
|
||||
} else {
|
||||
g.lobby.fullscreen = true
|
||||
g.lobby.setRect(0, 0, g.screenW, g.screenH)
|
||||
}
|
||||
|
||||
if true || availableWidth >= 150 { // TODO allow chat window to be repositioned
|
||||
statusBufferHeight := g.screenH - gameBufferHeight - bufferPadding*3
|
||||
|
||||
g.statusBuffer.docked = true
|
||||
g.statusBuffer.setRect(g.screenW-statusBufferWidth, g.screenH-(statusBufferHeight), statusBufferWidth, statusBufferHeight)
|
||||
g.statusBuffer.setRect((g.screenW-statusBufferWidth)+bufferPadding, bufferPadding, statusBufferWidth-(bufferPadding*2), statusBufferHeight)
|
||||
|
||||
g.gameBuffer.docked = true
|
||||
g.gameBuffer.setRect(g.screenW-statusBufferWidth, 0, statusBufferWidth, statusBufferHeight)
|
||||
g.gameBuffer.setRect((g.screenW-statusBufferWidth)+bufferPadding, (g.screenH-(gameBufferHeight))-bufferPadding, statusBufferWidth-(bufferPadding*2), gameBufferHeight)
|
||||
} else {
|
||||
// Clamp buffer position.
|
||||
bx, by := g.statusBuffer.x, g.statusBuffer.y
|
||||
|
|
|
@ -2,6 +2,7 @@ package game
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
|
@ -36,6 +37,8 @@ type lobby struct {
|
|||
x, y int
|
||||
w, h int
|
||||
|
||||
fullscreen bool
|
||||
|
||||
padding float64
|
||||
entryH float64
|
||||
buttonBarHeight int
|
||||
|
@ -102,9 +105,9 @@ func (l *lobby) _drawBufferButtons() {
|
|||
l.bufferButtons.Fill(frameColor)
|
||||
|
||||
// Draw border
|
||||
for ly := 0; ly < 2; ly++ {
|
||||
for ly := 0; ly < 1; ly++ {
|
||||
for lx := 0; lx < l.w; lx++ {
|
||||
l.bufferButtons.Set(lx, ly, triangleA)
|
||||
l.bufferButtons.Set(lx, ly, borderColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,20 +118,20 @@ func (l *lobby) _drawBufferButtons() {
|
|||
// Draw border
|
||||
if i > 0 {
|
||||
for ly := 0; ly < l.buttonBarHeight; ly++ {
|
||||
for lx := buttonWidth * i; lx < (buttonWidth*i)+2; lx++ {
|
||||
l.bufferButtons.Set(lx, ly, triangleA)
|
||||
for lx := buttonWidth * i; lx < (buttonWidth*i)+1; lx++ {
|
||||
l.bufferButtons.Set(lx, ly, borderColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
bounds := text.BoundString(mediumFont, button.label)
|
||||
|
||||
labelColor := triangleA
|
||||
labelColor := lightCheckerColor
|
||||
|
||||
img := ebiten.NewImage(bounds.Dx()*2, bounds.Dy()*2)
|
||||
text.Draw(img, button.label, mediumFont, 0, bounds.Dy(), labelColor)
|
||||
text.Draw(img, button.label, mediumFont, 0, standardLineHeight, labelColor)
|
||||
|
||||
l.op.GeoM.Reset()
|
||||
l.op.GeoM.Translate(float64(buttonWidth*i)+float64((buttonWidth-bounds.Dx())/2), float64(l.buttonBarHeight-bounds.Dy())/2-float64(bounds.Dy()/2))
|
||||
l.op.GeoM.Translate(float64(buttonWidth*i)+float64((buttonWidth-bounds.Dx())/2), float64(l.buttonBarHeight-standardLineHeight*1.5)/2)
|
||||
l.bufferButtons.DrawImage(img, l.op)
|
||||
}
|
||||
}
|
||||
|
@ -159,53 +162,55 @@ func (l *lobby) drawBuffer() {
|
|||
}
|
||||
} else {
|
||||
var img *ebiten.Image
|
||||
drawEntry := func(cx float64, cy float64, colA string, colB string, colC string, highlight bool) {
|
||||
boundsA := text.BoundString(mediumFont, colA)
|
||||
boundsB := text.BoundString(mediumFont, colB)
|
||||
boundsC := text.BoundString(mediumFont, colC)
|
||||
y := (boundsA.Dy() + boundsB.Dy() + boundsC.Dy()) / 3 // TODO this isn't correct
|
||||
|
||||
drawEntry := func(cx float64, cy float64, colA string, colB string, colC string, highlight bool, title bool) {
|
||||
labelColor := triangleA
|
||||
if highlight {
|
||||
labelColor = color.RGBA{200, 200, 60, 255}
|
||||
labelColor = lightCheckerColor
|
||||
} else if title {
|
||||
labelColor = lightCheckerColor
|
||||
}
|
||||
|
||||
selectedBorderColor := triangleB
|
||||
|
||||
img = ebiten.NewImage(l.w-int(l.padding*2), int(l.entryH))
|
||||
if highlight {
|
||||
highlightColor := color.RGBA{triangleA.R, triangleA.G, triangleA.B, 15}
|
||||
img.SubImage(image.Rect(0, 0, l.w, int(l.entryH))).(*ebiten.Image).Fill(highlightColor)
|
||||
|
||||
div := 1.75
|
||||
highlightBorderColor := color.RGBA{uint8(float64(frameColor.R) / div), uint8(float64(frameColor.G) / div), uint8(float64(frameColor.B) / div), 200}
|
||||
for x := 0; x < l.w; x++ {
|
||||
img.Set(x, 0, selectedBorderColor)
|
||||
img.Set(x, int(l.entryH)-1, selectedBorderColor)
|
||||
img.Set(x, 0, highlightBorderColor)
|
||||
img.Set(x, int(l.entryH)-1, highlightBorderColor)
|
||||
}
|
||||
for by := 0; by < int(l.entryH)-1; by++ {
|
||||
img.Set(0, by, selectedBorderColor)
|
||||
img.Set(l.w-(int(l.padding)*2)-1, by, selectedBorderColor)
|
||||
img.Set(0, by, highlightBorderColor)
|
||||
img.Set(l.w-(int(l.padding)*2)-1, by, highlightBorderColor)
|
||||
}
|
||||
}
|
||||
|
||||
text.Draw(img, colA, mediumFont, 4, y+2, labelColor)
|
||||
text.Draw(img, colB, mediumFont, int(250*ebiten.DeviceScaleFactor()), y+2, labelColor)
|
||||
text.Draw(img, colC, mediumFont, int(500*ebiten.DeviceScaleFactor()), y+2, labelColor)
|
||||
text.Draw(img, colA, mediumFont, 4, standardLineHeight, labelColor)
|
||||
text.Draw(img, colB, mediumFont, int(250*ebiten.DeviceScaleFactor()), standardLineHeight, labelColor)
|
||||
text.Draw(img, colC, mediumFont, int(500*ebiten.DeviceScaleFactor()), standardLineHeight, labelColor)
|
||||
|
||||
l.op.GeoM.Reset()
|
||||
l.op.GeoM.Translate(cx, cy)
|
||||
l.buffer.DrawImage(img, l.op)
|
||||
}
|
||||
|
||||
titleOffset := 2.0
|
||||
|
||||
if len(l.who) == 0 {
|
||||
drawEntry(l.padding, l.padding, "Loading...", "Please wait...", "Loading...", false)
|
||||
drawEntry(l.padding, l.padding-titleOffset, "Loading...", "Please wait...", "Loading...", false, true)
|
||||
return
|
||||
}
|
||||
|
||||
for ly := -3; ly < -1; ly++ {
|
||||
for lx := 0; lx < l.w-int(l.padding*2); lx++ {
|
||||
l.buffer.Set(int(l.padding)+lx, int(l.padding)+int(l.entryH)+ly, triangleA)
|
||||
for ly := -2; ly < -1; ly++ {
|
||||
for lx := 0; lx < l.w; lx++ {
|
||||
l.buffer.Set(lx, int(l.padding)+int(l.entryH)+ly, borderColor)
|
||||
}
|
||||
}
|
||||
|
||||
cx, cy := 0.0, 0.0 // Cursor
|
||||
drawEntry(cx+l.padding, cy+l.padding, "Username", "Rating (Experience)", "Status", false)
|
||||
drawEntry(cx+l.padding, cy+l.padding-titleOffset, "Username", "Rating (Experience)", "Status", false, true)
|
||||
cy += l.entryH
|
||||
i := 0
|
||||
for _, who := range l.who {
|
||||
|
@ -219,7 +224,7 @@ func (l *lobby) drawBuffer() {
|
|||
status = "Ready to play"
|
||||
}
|
||||
|
||||
drawEntry(cx+l.padding, cy+l.padding, who.Username, details, status, i == l.selected)
|
||||
drawEntry(cx+l.padding, cy+l.padding, who.Username, details, status, i == l.selected, false)
|
||||
|
||||
cy += l.entryH
|
||||
}
|
||||
|
@ -274,7 +279,6 @@ func (l *lobby) setRect(x, y, w, h int) {
|
|||
s := ebiten.DeviceScaleFactor()
|
||||
l.padding = 4 * s
|
||||
l.entryH = 32 * s
|
||||
l.buttonBarHeight = int(200 * s)
|
||||
|
||||
if l.w != w || l.h != h {
|
||||
l.buffer = ebiten.NewImage(w, h)
|
||||
|
|
|
@ -28,7 +28,7 @@ const (
|
|||
|
||||
const (
|
||||
monoLineHeight = 14
|
||||
standardLineHeight = 22
|
||||
standardLineHeight = 24
|
||||
)
|
||||
|
||||
type tabbedBuffers struct {
|
||||
|
@ -105,14 +105,6 @@ func (t *tabbedBuffers) setRect(x, y, w, h int) {
|
|||
}
|
||||
|
||||
if t.w != w {
|
||||
if w > 200 {
|
||||
t.padding = 2
|
||||
} else if w > 100 {
|
||||
t.padding = 1
|
||||
} else {
|
||||
t.padding = 0
|
||||
}
|
||||
|
||||
t.wrapWidth = (w - (t.padding * 4)) / t.chatFontSize
|
||||
for _, b := range t.buffers {
|
||||
b.wrapDirty = true
|
||||
|
@ -122,40 +114,43 @@ func (t *tabbedBuffers) setRect(x, y, w, h int) {
|
|||
t.x, t.y, t.w, t.h = x, y, w, h
|
||||
}
|
||||
|
||||
func (t *tabbedBuffers) linesVisible() int {
|
||||
lines := (t.h - (t.padding * 2)) / t.chatLineHeight
|
||||
// Leave space for the input buffer.
|
||||
if t.acceptInput {
|
||||
if lines > 1 {
|
||||
lines--
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (t *tabbedBuffers) drawBuffer() {
|
||||
t.buffer.Fill(color.Black)
|
||||
// Draw background.
|
||||
t.buffer.Fill(color.RGBA{0, 0, 0, 100})
|
||||
|
||||
// Draw content.
|
||||
|
||||
textColor := triangleALight
|
||||
|
||||
/*sub := t.buffer.SubImage(image.Rect(1, 1, t.w-1, t.h-1)).(*ebiten.Image)
|
||||
sub.Fill(frameColor)*/
|
||||
|
||||
b := t.buffers[0]
|
||||
|
||||
l := len(b.contentWrapped)
|
||||
|
||||
showLines := t.h / t.chatLineHeight
|
||||
if showLines > 1 {
|
||||
showLines--
|
||||
}
|
||||
if t.acceptInput {
|
||||
// Leave space for the input buffer.
|
||||
if showLines > 1 {
|
||||
showLines--
|
||||
}
|
||||
}
|
||||
|
||||
showLines := t.linesVisible()
|
||||
if l < showLines {
|
||||
showLines = l
|
||||
}
|
||||
|
||||
for i := 0; i < showLines; i++ {
|
||||
line := b.contentWrapped[l-showLines+i]
|
||||
|
||||
text.Draw(t.buffer, line, t.chatFont, t.padding*2, t.padding+(t.chatLineHeight*(i+1)), textColor)
|
||||
text.Draw(t.buffer, line, t.chatFont, t.padding*2, t.chatLineHeight*(i+1), textColor)
|
||||
}
|
||||
|
||||
// Draw input buffer.
|
||||
if t.acceptInput {
|
||||
text.Draw(t.buffer, "> "+string(t.inputBuffer), t.chatFont, t.padding*2, t.h-(t.padding*2), textColor)
|
||||
text.Draw(t.buffer, "> "+string(t.inputBuffer), t.chatFont, t.padding*2, t.h-(t.padding*4), textColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -23,7 +23,7 @@ require (
|
|||
golang.org/x/exp v0.0.0-20211111183329-cb5df436b1a8 // indirect
|
||||
golang.org/x/mobile v0.0.0-20211109191125-d61a72f26a1a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e // indirect
|
||||
golang.org/x/sys v0.0.0-20211112193437-faf0a1b62c6b // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -441,8 +441,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e h1:zeJt6jBtVDK23XK9QXcmG0FvO0elikp0dYZQZOeL1y0=
|
||||
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211112193437-faf0a1b62c6b h1:uo+9AuR+gDt/gdj+1BaLhdOHsaGI6YU6585IiDcLrFE=
|
||||
golang.org/x/sys v0.0.0-20211112193437-faf0a1b62c6b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
|
Loading…
Reference in a new issue