@ -0,0 +1,4 @@ |
|||
.idea |
|||
.DS_Store |
|||
|
|||
examples |
@ -0,0 +1,2 @@ |
|||
Andy Williams <andy@andy.xyz> |
|||
Steve OConnor <steveoc64@gmail.com> |
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,26 @@ |
|||
Copyright (C) 2018-2020 Fyne.io developers (see AUTHORS) |
|||
All rights reserved. |
|||
|
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
* Neither the name of Fyne.io nor the |
|||
names of its contributors may be used to endorse or promote products |
|||
derived from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY |
|||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
@ -0,0 +1,72 @@ |
|||
<p align="center"> |
|||
<a href="https://goreportcard.com/report/github.com/fyne-io/examples"><img src="https://goreportcard.com/badge/github.com/fyne-io/examples" alt="Code Status" /></a> |
|||
<a href='http://gophers.slack.com/messages/fyne'><img src='https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=blue' alt='Join us on Slack' /></a> |
|||
<a href='https://fossfi.sh/support-fyneio'><img src='https://img.shields.io/badge/$-support_us-orange.svg?labelWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAkCAYAAADPRbkKAAAABmJLR0QA7wAyAD/CTveyAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAAB3RJTUUH4wMVCQ4LeuPReAAABDFJREFUWMPVmX9oVWUYxz/3tr5jbX9oYiZWSmNgoYYlUVqraYvIamlCg8pqUBTZr38qMhZZ0A8oIqKIiLJoJtEIsR+zki1mGqQwZTYkImtlgk5Wy7jPnbv90XPgcD3n3Lvdy731wOGc+77nec73Oe/zfp/nOTeVy+WolphZC3CVH9N9OAsMAzuBLkkHkmykKu2AmQGsAjYAC4pQ+QJ4WNJg1R0ws3pgI3DTJFVPAM8AGyRNVMUBB98DLCvBzLtAh6QTwUC6gmGzuUTwAGuBR8MD6QpFzwPAypi5v4HXgRXAEg+vLQm2njSzCyoWQmbWAPwAzIqY/gm4UdJAhN5qoAuojdCbAD4BVlViBdbGgJ8A2gPwZtZsZvea2XwASd1AZ4zNNHA90FgJB9ri6FHStw7+WaAPeA3YbWaBzovAiF/nfMUC+QD4sRIOLIkZ73Hws4BHQuOneY7A2aY/CHdgL3AtsBhYD9TVVMCB0+O2h59nRJBJfej6WOj6Bj8C2VcpFoqSC/38PbArb24zQDabBWhOsFFbNAuZ2UygxZcvcHwPsEPScILecaAuYmoUaJR01MymAU8AFzm7vCxp3MxagW0xpv8EzinogLPC40A7cGpMKHQB6yX9FqE/ACyKMb8VWC0pG6E3D+gF5sbo9km6Ml0A/N3Ad8BtMeABBNwB9JrZ7Ij5PQmPuA7YZWZXmFnan1ljZrf65p2boDuQmInNrAN4I29DJUmT02C+bCtiL/QCo2Y2BIwB7wFzCuj1xGZiM7vcb9gOTAMunUTZMVPSkZCtJuBAmQngIHCupImaCPApoBVokvSrj7UDm4o0Pg84Evo97EkoVUYHXgjK6pNWwB1I5dfdZtbtjUghWSppZ0ivDvirjA4MAosCfCeFhaRcPvhQLV6QbYGhvLHZZQRvwO1hfJNJZFu9qkySNyUdyxu7pkzgc8BDknZPuSMzs8XA58AZEdPbgTZJY3ld2GABOixWXpH0YMk9sZlN96zZ6hn2F+AdYFM4IZlZLfBRQiMzGXlaUmdFmnpPSC3A814alCJjwDpJG+NuqCkC0HzgLi+L651RvgTeAu4ELnHazAJnedkwpwzvoh+4RdLPid+FMpnMDElHY8CvAd4GGiKmR4A1DvwpYHkZabLTO7KCkgYeiwG/3Iu0hoQ6fwvwh6QVwFKn2tEpgB4BPgSaJS0oFnywAnuB5yR15bHNV6HPfUnyqaSVIV0BlwEXe51zNtAYehGHgMNOyfu9F+iXND6V5UplMplXgfuAj4HPgIXAPcXsj5CcKelwNbqiVCaTOd/jrhRZJumbajiQlrQf6OZ/KkEpsQ74vQQ7w1V1QNIh4Gbg+BRs9BXi6kqsAJK+Bq52hihWxoH7/wshFDixw786vF9kmm+TtK+aDsTWQma2EOjg379/zgNO8akh73NfknSw2pv4H3Ayg0FmbTMRAAAAAElFTkSuQmCC' alt='Support Fyne.io' /></a> |
|||
|
|||
<br /> |
|||
<a href="https://travis-ci.com/github/fyne-io/examples"><img src="https://travis-ci.com/github/fyne-io/examples.svg" alt="Build Status" /></a> |
|||
<a href='https://coveralls.io/github/fyne-io/examples?branch=develop'><img src='https://coveralls.io/repos/github/fyne-io/examples/badge.svg?branch=develop' alt='Coverage Status' /></a> |
|||
</p> |
|||
|
|||
# Fyne Examples |
|||
|
|||
Here we will gather example apps that use the [Fyne](http://fyne.io) toolkit. |
|||
|
|||
You can start the main example app that links to all the others by running |
|||
|
|||
```bash |
|||
go run . |
|||
``` |
|||
|
|||
All these examples are fully scalable - try setting the `FYNE_SCALE` |
|||
environment variable to override the detection of your screen's density. |
|||
Many also respond to the current theme (this is default behaviour for |
|||
apps built using Fyne widgets) - you can try setting `FYNE_THEME=light` |
|||
to change from the default dark theme. |
|||
|
|||
## Widget based examples |
|||
|
|||
The following examples use mostly built in widgets making applications |
|||
trivial to build :). |
|||
|
|||
### Calculator |
|||
|
|||
Moved to [calculator repository](https://github.com/fyne-io/calculator/) |
|||
|
|||
### Bugs game (like MineSweeper) |
|||
|
|||
Hunt the squares to reveal everything apart from the bugs! |
|||
|
|||
![](img/bugs.png) |
|||
|
|||
### XKCD |
|||
|
|||
An XKCD comic browser with random and lookup features. |
|||
|
|||
![](img/xkcd.png) |
|||
|
|||
## Graphics based examples |
|||
|
|||
These examples use the Fyne canvas API to draw primitive shapes, |
|||
text and images to create custom user interfaces. |
|||
|
|||
### Clock |
|||
|
|||
A simple analog clock that matches the current theme. |
|||
|
|||
![](img/clock-dark.png) ![](img/clock-light.png) |
|||
|
|||
### Fractal |
|||
|
|||
A fractal viewer that can be panned and zoomed |
|||
|
|||
![](img/fractal.png) |
|||
|
|||
### Solitaire |
|||
|
|||
Moved to [solitaire repository](https://github.com/fyne-io/solitaire/) |
|||
|
|||
### Life |
|||
|
|||
Moved to [life repository](https://github.com/fyne-io/life/) |
|||
|
@ -0,0 +1,212 @@ |
|||
package bugs |
|||
|
|||
import ( |
|||
"fmt" |
|||
"math/rand" |
|||
"strings" |
|||
"time" |
|||
) |
|||
|
|||
type square struct { |
|||
bug bool |
|||
shown bool |
|||
flagged bool |
|||
|
|||
near int |
|||
} |
|||
|
|||
type board struct { |
|||
height, width int |
|||
|
|||
win, lose func() |
|||
|
|||
bugCount int |
|||
flagCount int |
|||
bugs [][]square |
|||
} |
|||
|
|||
func (b *board) countHidden() int { |
|||
count := 0 |
|||
|
|||
for y := 0; y < b.height; y++ { |
|||
for x := 0; x < b.width; x++ { |
|||
item := b.bugs[y][x] |
|||
|
|||
if !item.shown { |
|||
count++ |
|||
} |
|||
} |
|||
} |
|||
|
|||
return count |
|||
} |
|||
|
|||
func (b *board) load(count int) { |
|||
b.bugCount = count |
|||
b.flagCount = 0 |
|||
if b.bugs == nil { |
|||
b.bugs = make([][]square, b.height) |
|||
|
|||
for y := 0; y < b.height; y++ { |
|||
b.bugs[y] = make([]square, b.width) |
|||
} |
|||
} else { |
|||
for y := 0; y < b.height; y++ { |
|||
for x := 0; x < b.width; x++ { |
|||
b.bugs[y][x].shown = false |
|||
b.bugs[y][x].bug = false |
|||
b.bugs[y][x].flagged = false |
|||
b.bugs[y][x].near = 0 |
|||
} |
|||
} |
|||
} |
|||
|
|||
for i := 0; i < count; i++ { |
|||
x := rand.Intn(b.width) |
|||
y := rand.Intn(b.height) |
|||
|
|||
if b.bugs[y][x].bug { |
|||
i-- |
|||
} else { |
|||
b.setMine(x, y) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (b *board) incSquare(x, y int) { |
|||
if x < 0 || y < 0 { |
|||
return |
|||
} |
|||
if x >= b.width || y >= b.height { |
|||
return |
|||
} |
|||
|
|||
if b.bugs[y][x].bug { |
|||
return |
|||
} |
|||
b.bugs[y][x].near++ |
|||
} |
|||
|
|||
func (b *board) setMine(x, y int) { |
|||
if b.bugs[y][x].bug { |
|||
return |
|||
} |
|||
b.bugs[y][x].bug = true |
|||
|
|||
b.incSquare(x-1, y-1) |
|||
b.incSquare(x, y-1) |
|||
b.incSquare(x+1, y-1) |
|||
|
|||
b.incSquare(x-1, y) |
|||
b.incSquare(x+1, y) |
|||
|
|||
b.incSquare(x-1, y+1) |
|||
b.incSquare(x, y+1) |
|||
b.incSquare(x+1, y+1) |
|||
} |
|||
|
|||
func (b *board) reveal(x, y int) { |
|||
if x < 0 || y < 0 { |
|||
return |
|||
} |
|||
if x >= b.width || y >= b.height { |
|||
return |
|||
} |
|||
|
|||
sq := b.bugs[y][x] |
|||
if sq.shown || sq.flagged { |
|||
return |
|||
} |
|||
b.bugs[y][x].shown = true |
|||
|
|||
if sq.bug { |
|||
if b.lose != nil { |
|||
b.lose() |
|||
} |
|||
return |
|||
} |
|||
|
|||
if sq.near == 0 { |
|||
b.reveal(x-1, y-1) |
|||
b.reveal(x, y-1) |
|||
b.reveal(x+1, y-1) |
|||
b.reveal(x-1, y) |
|||
b.reveal(x+1, y) |
|||
b.reveal(x-1, y+1) |
|||
b.reveal(x, y+1) |
|||
b.reveal(x+1, y+1) |
|||
} |
|||
|
|||
if b.countHidden() == b.bugCount && b.win != nil { |
|||
b.win() |
|||
} |
|||
} |
|||
|
|||
func (b *board) flag(x, y int) { |
|||
if x < 0 || y < 0 { |
|||
return |
|||
} |
|||
if x >= b.width || y >= b.height { |
|||
return |
|||
} |
|||
|
|||
sq := b.bugs[y][x] |
|||
if sq.shown { |
|||
return |
|||
} |
|||
|
|||
if sq.flagged { |
|||
b.bugs[y][x].flagged = false |
|||
b.flagCount-- |
|||
} else { |
|||
b.bugs[y][x].flagged = true |
|||
b.flagCount++ |
|||
} |
|||
} |
|||
|
|||
func (b *board) flagged(x, y int) bool { |
|||
if x < 0 || y < 0 { |
|||
return false |
|||
} |
|||
if x >= b.width || y >= b.height { |
|||
return false |
|||
} |
|||
|
|||
sq := b.bugs[y][x] |
|||
return sq.flagged |
|||
} |
|||
|
|||
func (b *board) remaining() int { |
|||
return b.bugCount - b.flagCount |
|||
} |
|||
|
|||
func squareString(sq square) string { |
|||
if !sq.shown { |
|||
return "?" |
|||
} else if sq.bug { |
|||
return "*" |
|||
} else if sq.near == 0 { |
|||
return " " |
|||
} |
|||
|
|||
return fmt.Sprintf("%d", sq.near) |
|||
} |
|||
|
|||
func (b *board) String() string { |
|||
buf := strings.Builder{} |
|||
for y := 0; y < b.height; y++ { |
|||
for x := 0; x < b.width; x++ { |
|||
sq := b.bugs[y][x] |
|||
|
|||
buf.WriteString(squareString(sq)) |
|||
} |
|||
buf.WriteByte('\n') |
|||
} |
|||
|
|||
return buf.String() |
|||
} |
|||
|
|||
func newBoard(height, width int) *board { |
|||
rand.Seed(time.Now().Unix()) |
|||
return &board{height: height, width: width} |
|||
} |
@ -0,0 +1,111 @@ |
|||
package bugs |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/stretchr/testify/assert" |
|||
) |
|||
|
|||
func countMines(b *board) int { |
|||
count := 0 |
|||
|
|||
for y := 0; y < b.height; y++ { |
|||
for x := 0; x < b.width; x++ { |
|||
item := b.bugs[y][x] |
|||
|
|||
if item.bug { |
|||
count++ |
|||
} |
|||
} |
|||
} |
|||
|
|||
return count |
|||
} |
|||
|
|||
func TestBoard(t *testing.T) { |
|||
b := newBoard(10, 10) |
|||
|
|||
assert.Equal(t, 10, b.width) |
|||
|
|||
b.load(0) |
|||
assert.Equal(t, 10, len(b.bugs)) |
|||
assert.Equal(t, 10, len(b.bugs[0])) |
|||
|
|||
assert.Equal(t, 0, b.bugs[2][2].near) |
|||
assert.Equal(t, false, b.bugs[2][2].shown) |
|||
} |
|||
|
|||
func TestBoard_load(t *testing.T) { |
|||
b := newBoard(10, 10) |
|||
b.load(4) |
|||
|
|||
assert.Equal(t, 4, countMines(b)) |
|||
} |
|||
|
|||
func TestBoard_setMine(t *testing.T) { |
|||
b := newBoard(3, 3) |
|||
b.load(0) |
|||
b.setMine(1, 1) |
|||
|
|||
assert.True(t, b.bugs[1][1].bug) |
|||
|
|||
assert.Equal(t, 1, b.bugs[0][0].near) |
|||
assert.Equal(t, 1, b.bugs[0][1].near) |
|||
assert.Equal(t, 1, b.bugs[0][2].near) |
|||
assert.Equal(t, 1, b.bugs[1][0].near) |
|||
assert.Equal(t, 1, b.bugs[1][2].near) |
|||
assert.Equal(t, 1, b.bugs[2][0].near) |
|||
assert.Equal(t, 1, b.bugs[2][1].near) |
|||
assert.Equal(t, 1, b.bugs[2][2].near) |
|||
} |
|||
|
|||
func TestBoard_setMines(t *testing.T) { |
|||
b := newBoard(3, 4) |
|||
b.load(0) |
|||
b.setMine(1, 1) |
|||
b.setMine(2, 1) |
|||
|
|||
assert.Equal(t, 2, countMines(b)) |
|||
assert.True(t, b.bugs[1][1].bug) |
|||
assert.True(t, b.bugs[1][2].bug) |
|||
|
|||
assert.Equal(t, 1, b.bugs[0][0].near) |
|||
assert.Equal(t, 2, b.bugs[0][1].near) |
|||
assert.Equal(t, 2, b.bugs[0][2].near) |
|||
assert.Equal(t, 1, b.bugs[0][3].near) |
|||
assert.Equal(t, 1, b.bugs[1][0].near) |
|||
assert.Equal(t, 1, b.bugs[1][3].near) |
|||
assert.Equal(t, 1, b.bugs[2][0].near) |
|||
assert.Equal(t, 2, b.bugs[2][1].near) |
|||
assert.Equal(t, 2, b.bugs[2][2].near) |
|||
assert.Equal(t, 1, b.bugs[2][3].near) |
|||
} |
|||
|
|||
func TestBoard_remaining(t *testing.T) { |
|||
b := newBoard(3, 3) |
|||
b.load(1) |
|||
|
|||
assert.Equal(t, 1, b.remaining()) |
|||
b.flag(0, 0) |
|||
assert.Equal(t, 0, b.remaining()) |
|||
} |
|||
|
|||
func TestBoard_reveal(t *testing.T) { |
|||
b := newBoard(3, 3) |
|||
b.load(0) |
|||
b.setMine(1, 1) |
|||
|
|||
b.reveal(2, 1) |
|||
assert.True(t, b.bugs[1][2].shown) |
|||
} |
|||
|
|||
func TestBoard_revealCascade(t *testing.T) { |
|||
b := newBoard(3, 4) |
|||
b.load(0) |
|||
b.setMine(1, 1) |
|||
|
|||
b.reveal(3, 1) |
|||
assert.True(t, b.bugs[0][3].shown) |
|||
assert.True(t, b.bugs[1][3].shown) |
|||
assert.True(t, b.bugs[2][3].shown) |
|||
} |
After Width: | Height: | Size: 512 B |
@ -0,0 +1,20 @@ |
|||
// auto-generated
|
|||
|
|||
package bugs |
|||
|
|||
import "fyne.io/fyne/v2" |
|||
|
|||
var codeIcon = &fyne.StaticResource{ |
|||
StaticName: "code.svg", |
|||
StaticContent: []byte{ |
|||
60, 115, 118, 103, 32, 120, 109, 108, 110, 115, 61, 34, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 119, 51, 46, 111, 114, 103, 47, 50, 48, 48, 48, 47, 115, 118, 103, 34, 32, 119, 105, 100, 116, 104, 61, 34, 50, 52, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 50, 52, 34, 32, 118, 105, 101, 119, 66, 111, 120, 61, 34, 48, 32, 48, 32, 50, 52, 32, 50, 52, 34, 62, 60, 112, 97, 116, 104, 32, 102, 105, 108, 108, 61, 34, 110, 111, 110, 101, 34, 32, 100, 61, 34, 77, 48, 32, 48, 104, 50, 52, 118, 50, 52, 72, 48, 86, 48, 122, 34, 47, 62, 60, 112, 97, 116, 104, 32, 100, 61, 34, 77, 57, 46, 52, 32, 49, 54, 46, 54, 76, 52, 46, 56, 32, 49, 50, 108, 52, 46, 54, 45, 52, 46, 54, 76, 56, 32, 54, 108, 45, 54, 32, 54, 32, 54, 32, 54, 32, 49, 46, 52, 45, 49, 46, 52, 122, 109, 53, 46, 50, 32, 48, 108, 52, 46, 54, 45, 52, 46, 54, 45, 52, 46, 54, 45, 52, 46, 54, 76, 49, 54, 32, 54, 108, 54, 32, 54, 45, 54, 32, 54, 45, 49, 46, 52, 45, 49, 46, 52, 122, 34, 47, 62, 60, 47, 115, 118, 103, 62}} |
|||
|
|||
var bugIcon = &fyne.StaticResource{ |
|||
StaticName: "bug.svg", |
|||
StaticContent: []byte{ |
|||
60, 115, 118, 103, 32, 120, 109, 108, 110, 115, 61, 34, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 119, 51, 46, 111, 114, 103, 47, 50, 48, 48, 48, 47, 115, 118, 103, 34, 32, 119, 105, 100, 116, 104, 61, 34, 50, 52, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 50, 52, 34, 32, 118, 105, 101, 119, 66, 111, 120, 61, 34, 48, 32, 48, 32, 50, 52, 32, 50, 52, 34, 62, 60, 112, 97, 116, 104, 32, 100, 61, 34, 77, 48, 32, 48, 104, 50, 52, 118, 50, 52, 72, 48, 122, 34, 32, 102, 105, 108, 108, 61, 34, 110, 111, 110, 101, 34, 47, 62, 60, 112, 97, 116, 104, 32, 100, 61, 34, 77, 50, 48, 32, 56, 104, 45, 50, 46, 56, 49, 99, 45, 46, 52, 53, 45, 46, 55, 56, 45, 49, 46, 48, 55, 45, 49, 46, 52, 53, 45, 49, 46, 56, 50, 45, 49, 46, 57, 54, 76, 49, 55, 32, 52, 46, 52, 49, 32, 49, 53, 46, 53, 57, 32, 51, 108, 45, 50, 46, 49, 55, 32, 50, 46, 49, 55, 67, 49, 50, 46, 57, 54, 32, 53, 46, 48, 54, 32, 49, 50, 46, 52, 57, 32, 53, 32, 49, 50, 32, 53, 99, 45, 46, 52, 57, 32, 48, 45, 46, 57, 54, 46, 48, 54, 45, 49, 46, 52, 49, 46, 49, 55, 76, 56, 46, 52, 49, 32, 51, 32, 55, 32, 52, 46, 52, 49, 108, 49, 46, 54, 50, 32, 49, 46, 54, 51, 67, 55, 46, 56, 56, 32, 54, 46, 53, 53, 32, 55, 46, 50, 54, 32, 55, 46, 50, 50, 32, 54, 46, 56, 49, 32, 56, 72, 52, 118, 50, 104, 50, 46, 48, 57, 99, 45, 46, 48, 53, 46, 51, 51, 45, 46, 48, 57, 46, 54, 54, 45, 46, 48, 57, 32, 49, 118, 49, 72, 52, 118, 50, 104, 50, 118, 49, 99, 48, 32, 46, 51, 52, 46, 48, 52, 46, 54, 55, 46, 48, 57, 32, 49, 72, 52, 118, 50, 104, 50, 46, 56, 49, 99, 49, 46, 48, 52, 32, 49, 46, 55, 57, 32, 50, 46, 57, 55, 32, 51, 32, 53, 46, 49, 57, 32, 51, 115, 52, 46, 49, 53, 45, 49, 46, 50, 49, 32, 53, 46, 49, 57, 45, 51, 72, 50, 48, 118, 45, 50, 104, 45, 50, 46, 48, 57, 99, 46, 48, 53, 45, 46, 51, 51, 46, 48, 57, 45, 46, 54, 54, 46, 48, 57, 45, 49, 118, 45, 49, 104, 50, 118, 45, 50, 104, 45, 50, 118, 45, 49, 99, 48, 45, 46, 51, 52, 45, 46, 48, 52, 45, 46, 54, 55, 45, 46, 48, 57, 45, 49, 72, 50, 48, 86, 56, 122, 109, 45, 54, 32, 56, 104, 45, 52, 118, 45, 50, 104, 52, 118, 50, 122, 109, 48, 45, 52, 104, 45, 52, 118, 45, 50, 104, 52, 118, 50, 122, 34, 47, 62, 60, 47, 115, 118, 103, 62}} |
|||
|
|||
var flagIcon = &fyne.StaticResource{ |
|||
StaticName: "flag.svg", |
|||
StaticContent: []byte{ |
|||
60, 115, 118, 103, 32, 120, 109, 108, 110, 115, 61, 34, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 119, 51, 46, 111, 114, 103, 47, 50, 48, 48, 48, 47, 115, 118, 103, 34, 32, 119, 105, 100, 116, 104, 61, 34, 50, 52, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 50, 52, 34, 32, 118, 105, 101, 119, 66, 111, 120, 61, 34, 48, 32, 48, 32, 50, 52, 32, 50, 52, 34, 62, 60, 112, 97, 116, 104, 32, 100, 61, 34, 77, 48, 32, 48, 104, 50, 52, 118, 50, 52, 72, 48, 122, 34, 32, 102, 105, 108, 108, 61, 34, 110, 111, 110, 101, 34, 47, 62, 60, 112, 97, 116, 104, 32, 100, 61, 34, 77, 49, 32, 50, 49, 104, 50, 50, 76, 49, 50, 32, 50, 32, 49, 32, 50, 49, 122, 109, 49, 50, 45, 51, 104, 45, 50, 118, 45, 50, 104, 50, 118, 50, 122, 109, 48, 45, 52, 104, 45, 50, 118, 45, 52, 104, 50, 118, 52, 122, 34, 47, 62, 60, 47, 115, 118, 103, 62}} |
@ -0,0 +1,124 @@ |
|||
package bugs |
|||
|
|||
import ( |
|||
"image/color" |
|||
|
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/canvas" |
|||
"fyne.io/fyne/v2/theme" |
|||
"fyne.io/fyne/v2/widget" |
|||
) |
|||
|
|||
type bugRenderer struct { |
|||
icon *canvas.Image |
|||
label *canvas.Text |
|||
|
|||
objects []fyne.CanvasObject |
|||
button *bugButton |
|||
} |
|||
|
|||
const bugSize = 18 |
|||
|
|||
// MinSize calculates the minimum size of a bug button. A fixed amount.
|
|||
func (b *bugRenderer) MinSize() fyne.Size { |
|||
return fyne.NewSize(bugSize+theme.Padding()*2, bugSize+theme.Padding()*2) |
|||
} |
|||
|
|||
// Layout the components of the widget
|
|||
func (b *bugRenderer) Layout(size fyne.Size) { |
|||
inner := size.Subtract(fyne.NewSize(theme.Padding()*2, theme.Padding()*2)) |
|||
b.icon.Resize(inner) |
|||
b.icon.Move(fyne.NewPos(theme.Padding(), theme.Padding())) |
|||
|
|||
textSize := size.Height * .67 |
|||
textMin := fyne.MeasureText(b.label.Text, textSize, fyne.TextStyle{Bold: true}) |
|||
|
|||
b.label.TextSize = textSize |
|||
b.label.Resize(fyne.NewSize(size.Width, textMin.Height)) |
|||
b.label.Move(fyne.NewPos(0, (size.Height-textMin.Height)/2)) |
|||
} |
|||
|
|||
// ApplyTheme is called when the bugButton may need to update it's look
|
|||
func (b *bugRenderer) ApplyTheme() { |
|||
b.label.Color = theme.ForegroundColor() |
|||
b.Refresh() |
|||
} |
|||
|
|||
func (b *bugRenderer) BackgroundColor() color.Color { |
|||
return theme.ButtonColor() |
|||
} |
|||
|
|||
func (b *bugRenderer) Refresh() { |
|||
b.label.Text = b.button.text |
|||
|
|||
b.icon.Hidden = b.button.icon == nil |
|||
if b.button.icon != nil { |
|||
b.icon.Resource = b.button.icon |
|||
} |
|||
|
|||
b.Layout(b.button.Size()) |
|||
canvas.Refresh(b.button) |
|||
} |
|||
|
|||
func (b *bugRenderer) Objects() []fyne.CanvasObject { |
|||
return b.objects |
|||
} |
|||
|
|||
func (b *bugRenderer) Destroy() { |
|||
} |
|||
|
|||
// bugButton widget is a scalable button that has a text label and icon and triggers an event func when clicked
|
|||
type bugButton struct { |
|||
widget.BaseWidget |
|||
text string |
|||
icon fyne.Resource |
|||
|
|||
tap func(bool) |
|||
} |
|||
|
|||
// Tapped is called when a regular tap is reported
|
|||
func (b *bugButton) Tapped(ev *fyne.PointEvent) { |
|||
b.tap(true) |
|||
} |
|||
|
|||
// TappedSecondary is called when an alternative tap is reported
|
|||
func (b *bugButton) TappedSecondary(ev *fyne.PointEvent) { |
|||
b.tap(false) |
|||
} |
|||
|
|||
func (b *bugButton) CreateRenderer() fyne.WidgetRenderer { |
|||
text := canvas.NewText(b.text, theme.ForegroundColor()) |
|||
text.Alignment = fyne.TextAlignCenter |
|||
text.TextStyle.Bold = true |
|||
|
|||
icon := canvas.NewImageFromResource(b.icon) |
|||
icon.FillMode = canvas.ImageFillContain |
|||
|
|||
objects := []fyne.CanvasObject{ |
|||
text, |
|||
icon, |
|||
} |
|||
|
|||
return &bugRenderer{icon, text, objects, b} |
|||
} |
|||
|
|||
// SetText allows the button label to be changed
|
|||
func (b *bugButton) SetText(text string) { |
|||
b.text = text |
|||
|
|||
b.Refresh() |
|||
} |
|||
|
|||
// SetIcon updates the icon on a label - pass nil to hide an icon
|
|||
func (b *bugButton) SetIcon(icon fyne.Resource) { |
|||
b.icon = icon |
|||
|
|||
b.Refresh() |
|||
} |
|||
|
|||
// newButton creates a new button widget with the specified label, themed icon and tap handler
|
|||
func newButton(label string, icon fyne.Resource, tap func(bool)) *bugButton { |
|||
button := &bugButton{text: label, icon: icon, tap: tap} |
|||
button.ExtendBaseWidget(button) |
|||
return button |
|||
} |
After Width: | Height: | Size: 230 B |
After Width: | Height: | Size: 188 B |
@ -0,0 +1,13 @@ |
|||
#!/bin/sh |
|||
|
|||
DIR=`dirname "$0"` |
|||
FILE=bundled.go |
|||
BIN=`go env GOPATH`/bin |
|||
|
|||
cd $DIR |
|||
rm $FILE |
|||
|
|||
$BIN/fyne bundle -package bugs -name codeIcon code.svg > $FILE |
|||
$BIN/fyne bundle -package bugs -name bugIcon -append bug.svg >> $FILE |
|||
$BIN/fyne bundle -package bugs -name flagIcon -append flag.svg >> $FILE |
|||
|
@ -0,0 +1,213 @@ |
|||
package bugs |
|||
|
|||
import ( |
|||
"fmt" |
|||
"image/color" |
|||
|
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/canvas" |
|||
"fyne.io/fyne/v2/container" |
|||
"fyne.io/fyne/v2/dialog" |
|||
"fyne.io/fyne/v2/theme" |
|||
"fyne.io/fyne/v2/widget" |
|||
) |
|||
|
|||
var bug, code, flag *theme.ThemedResource |
|||
|
|||
func init() { |
|||
bug = theme.NewThemedResource(bugIcon) |
|||
code = theme.NewThemedResource(codeIcon) |
|||
flag = theme.NewThemedResource(flagIcon) |
|||
} |
|||
|
|||
type gameRenderer struct { |
|||
grid *fyne.Container |
|||
header fyne.CanvasObject |
|||
|
|||
game *game |
|||
} |
|||
|
|||
func (g *gameRenderer) MinSize() fyne.Size { |
|||
return g.grid.MinSize().Add(fyne.NewSize(0, g.header.MinSize().Height)) |
|||
} |
|||
|
|||
func (g *gameRenderer) Layout(size fyne.Size) { |
|||
headerHeight := g.header.MinSize().Height |
|||
g.header.Resize(fyne.NewSize(size.Width, headerHeight)) |
|||
g.grid.Move(fyne.NewPos(0, headerHeight)) // TODO why ignored?
|
|||
gridSize := size.Subtract(fyne.NewSize(0, headerHeight)) |
|||
g.grid.Layout.Layout(g.grid.Objects, gridSize) |
|||
} |
|||
|
|||
func (g *gameRenderer) ApplyTheme() { |
|||
} |
|||
|
|||
func (g *gameRenderer) BackgroundColor() color.Color { |
|||
return theme.BackgroundColor() |
|||
} |
|||
|
|||
func (g *gameRenderer) Refresh() { |
|||
canvas.Refresh(g.grid) |
|||
} |
|||
|
|||
func (g *gameRenderer) Objects() []fyne.CanvasObject { |
|||
return []fyne.CanvasObject{g.grid, g.header} |
|||
} |
|||
|
|||
func (g *gameRenderer) Destroy() { |
|||
} |
|||
|
|||
type game struct { |
|||
widget.BaseWidget |
|||
board *board |
|||
remain *widget.Label |
|||
|
|||
grid *fyne.Container |
|||
window fyne.Window |
|||
} |
|||
|
|||
func (g *game) refreshSquare(x, y int) { |
|||
if x < 0 || y < 0 || x >= g.board.width || y >= g.board.height { |
|||
return |
|||
} |
|||
|
|||
sq := g.board.bugs[y][x] |
|||
i := y*g.board.width + x |
|||
button := g.grid.Objects[i].(*bugButton) |
|||
|
|||
if sq.flagged { |
|||
if button.icon == flag { |
|||
return |
|||
} |
|||
button.icon = flag |
|||
button.text = "" |
|||
} else if !sq.shown { |
|||
if button.icon == code { |
|||
return |
|||
} |
|||
button.icon = code |
|||
button.text = "" |
|||
} else if sq.bug { |
|||
if button.icon == bug { |
|||
return |
|||
} |
|||
button.icon = bug |
|||
button.text = "" |
|||
} else if button.icon == nil { |
|||
return |
|||
} else { |
|||
button.icon = nil |
|||
button.text = squareString(sq) |
|||
} |
|||
|
|||
button.Refresh() |
|||
} |
|||
|
|||
func (g *game) refreshAround(xp, yp, d int) { |
|||
x, y := xp-d, yp-d |
|||
for ; x < xp+d; x++ { |
|||
g.refreshSquare(x, y) |
|||
} |
|||
for ; y < yp+d; y++ { |
|||
g.refreshSquare(x, y) |
|||
} |
|||
for ; x > xp-d; x-- { |
|||
g.refreshSquare(x, y) |
|||
} |
|||
for ; y > yp-d; y-- { |
|||
g.refreshSquare(x, y) |
|||
} |
|||
} |
|||
|
|||
func (g *game) refreshFrom(x, y int) { |
|||
g.refreshSquare(x, y) |
|||
|
|||
for i := 1; i < int(fyne.Max(float32(g.board.width), float32(g.board.height))); i++ { |
|||
g.refreshAround(x, y, i) |
|||
} |
|||
} |
|||
|
|||
func (g *game) CreateRenderer() fyne.WidgetRenderer { |
|||
renderer := &gameRenderer{game: g} |
|||
title := widget.NewLabel("Hunt bugs!") |
|||
g.remain = widget.NewLabel("") |
|||
g.updateRemain() |
|||
renderer.header = container.NewBorder(nil, nil, title, g.remain) |
|||
|
|||
var buttons []fyne.CanvasObject |
|||
for y := 0; y < g.board.height; y++ { |
|||
for x := 0; x < g.board.width; x++ { |
|||
xx, yy := x, y |
|||
|
|||
buttons = append(buttons, newButton("", code, func(reveal bool) { |
|||
if reveal { |
|||
g.squareReveal(xx, yy) |
|||
} else { |
|||
g.squareFlagged(xx, yy) |
|||
} |
|||
|
|||
g.updateRemain() |
|||
})) |
|||
} |
|||
} |
|||
|
|||
renderer.grid = container.NewGridWithColumns(g.board.width, buttons...) |
|||
g.grid = renderer.grid |
|||
return renderer |
|||
} |
|||
|
|||
func (g *game) squareReveal(x, y int) { |
|||
if g.board.flagged(x, y) { |
|||
return |
|||
} |
|||
|
|||
g.board.reveal(x, y) |
|||
g.refreshFrom(x, y) |
|||
} |
|||
|
|||
func (g *game) squareFlagged(x, y int) { |
|||
g.board.flag(x, y) |
|||
g.refreshSquare(x, y) |
|||
} |
|||
|
|||
func (g *game) loseCallback(yes bool) { |
|||
if !yes { |
|||
return |
|||
} |
|||
|
|||
g.board.load(40) |
|||
g.updateRemain() |
|||
g.refreshFrom(g.board.width/2, g.board.height/2) |
|||
} |
|||
|
|||
func (g *game) win() { |
|||
dialog.ShowInformation("You won!", "Congratulations, you found all the bugs", g.window) |
|||
} |
|||
|
|||
func (g *game) lose() { |
|||
dialog.ShowConfirm("You lost!", "You hit a bug and lost the game, try again?", g.loseCallback, g.window) |
|||
} |
|||
|
|||
func (g *game) updateRemain() { |
|||
g.remain.SetText(fmt.Sprintf("remaining: %d", g.board.remaining())) |
|||
} |
|||
|
|||
func newGame(f *board) *game { |
|||
g := &game{board: f} |
|||
g.ExtendBaseWidget(g) |
|||
|
|||
return g |
|||
} |
|||
|
|||
// Show starts a new bugs game
|
|||
func Show(win fyne.Window) fyne.CanvasObject { |
|||
b := newBoard(20, 14) |
|||
game := newGame(b) |
|||
|
|||
b.win = game.win |
|||
b.lose = game.lose |
|||
b.load(40) |
|||
|
|||
game.window = win |
|||
return game |
|||
} |
@ -0,0 +1,132 @@ |
|||
package clock |
|||
|
|||
import ( |
|||
"math" |
|||
"time" |
|||
|
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/canvas" |
|||
"fyne.io/fyne/v2/container" |
|||
"fyne.io/fyne/v2/theme" |
|||
) |
|||
|
|||
type clockLayout struct { |
|||
hour, minute, second *canvas.Line |
|||
hourDot, secondDot, face *canvas.Circle |
|||
|
|||
canvas fyne.CanvasObject |
|||
stop bool |
|||
} |
|||
|
|||
func (c *clockLayout) rotate(hand *canvas.Line, middle fyne.Position, facePosition float64, offset, length float32) { |
|||
rotation := math.Pi * 2 / 60 * facePosition |
|||
x2 := length * float32(math.Sin(rotation)) |
|||
y2 := -length * float32(math.Cos(rotation)) |
|||
|
|||
offX := float32(0) |
|||
offY := float32(0) |
|||
if offset > 0 { |
|||
offX += offset * float32(math.Sin(rotation)) |
|||
offY += -offset * float32(math.Cos(rotation)) |
|||
} |
|||
|
|||
hand.Position1 = fyne.NewPos(middle.X+offX, middle.Y+offY) |
|||
hand.Position2 = fyne.NewPos(middle.X+offX+x2, middle.Y+offY+y2) |
|||
hand.Refresh() |
|||
} |
|||
|
|||
func (c *clockLayout) Layout(_ []fyne.CanvasObject, size fyne.Size) { |
|||
diameter := fyne.Min(size.Width, size.Height) |
|||
radius := diameter / 2 |
|||
dotRadius := radius / 12 |
|||
smallDotRadius := dotRadius / 8 |
|||
|
|||
stroke := diameter / 40 |
|||
midStroke := diameter / 90 |
|||
smallStroke := diameter / 200 |
|||
|
|||
size = fyne.NewSize(diameter, diameter) |
|||
middle := fyne.NewPos(size.Width/2, size.Height/2) |
|||
topleft := fyne.NewPos(middle.X-radius, middle.Y-radius) |
|||
|
|||
c.face.Resize(size) |
|||
c.face.Move(topleft) |
|||
|
|||
c.hour.StrokeWidth = stroke |
|||
c.rotate(c.hour, middle, float64((time.Now().Hour()%12)*5)+(float64(time.Now().Minute())/12), dotRadius, radius/2) |
|||
c.minute.StrokeWidth = midStroke |
|||
c.rotate(c.minute, middle, float64(time.Now().Minute())+(float64(time.Now().Second())/60), dotRadius, radius*.9) |
|||
c.second.StrokeWidth = smallStroke |
|||
c.rotate(c.second, middle, float64(time.Now().Second()), 0, radius-3) |
|||
|
|||
c.hourDot.StrokeWidth = stroke |
|||
c.hourDot.Resize(fyne.NewSize(dotRadius*2, dotRadius*2)) |
|||
c.hourDot.Move(fyne.NewPos(middle.X-dotRadius, middle.Y-dotRadius)) |
|||
c.secondDot.StrokeWidth = smallStroke |
|||
c.secondDot.Resize(fyne.NewSize(smallDotRadius*2, smallDotRadius*2)) |
|||
c.secondDot.Move(fyne.NewPos(middle.X-smallDotRadius, middle.Y-smallDotRadius)) |
|||
c.face.StrokeWidth = smallStroke |
|||
} |
|||
|
|||
func (c *clockLayout) MinSize(_ []fyne.CanvasObject) fyne.Size { |
|||
return fyne.NewSize(200, 200) |
|||
} |
|||
|
|||
func (c *clockLayout) render() *fyne.Container { |
|||
c.hourDot = &canvas.Circle{StrokeColor: theme.ForegroundColor(), StrokeWidth: 5} |
|||
c.secondDot = &canvas.Circle{StrokeColor: theme.PrimaryColor(), StrokeWidth: 3} |
|||
c.face = &canvas.Circle{StrokeColor: theme.ForegroundColor(), StrokeWidth: 1} |
|||
|
|||
c.hour = &canvas.Line{StrokeColor: theme.ForegroundColor(), StrokeWidth: 5} |
|||
c.minute = &canvas.Line{StrokeColor: theme.ForegroundColor(), StrokeWidth: 3} |
|||
c.second = &canvas.Line{StrokeColor: theme.PrimaryColor(), StrokeWidth: 1} |
|||
|
|||
container := container.NewWithoutLayout(c.hourDot, c.secondDot, c.face, c.hour, c.minute, c.second) |
|||
container.Layout = c |
|||
|
|||
c.canvas = container |
|||
return container |
|||
} |
|||
|
|||
func (c *clockLayout) animate(co fyne.CanvasObject) { |
|||
tick := time.NewTicker(time.Second) |
|||
go func() { |
|||
for !c.stop { |
|||
c.Layout(nil, co.Size()) |
|||
canvas.Refresh(c.canvas) |
|||
<-tick.C |
|||
} |
|||
}() |
|||
} |
|||
|
|||
func (c *clockLayout) applyTheme(_ fyne.Settings) { |
|||
c.hourDot.StrokeColor = theme.ForegroundColor() |
|||
c.secondDot.StrokeColor = theme.PrimaryColor() |
|||
c.face.StrokeColor = theme.ForegroundColor() |
|||
|
|||
c.hour.StrokeColor = theme.ForegroundColor() |
|||
c.minute.StrokeColor = theme.ForegroundColor() |
|||
c.second.StrokeColor = theme.PrimaryColor() |
|||
} |
|||
|
|||
// Show loads a clock example window for the specified app context
|
|||
func Show(win fyne.Window) fyne.CanvasObject { |
|||
clock := &clockLayout{} |
|||
//clockWindow.SetOnClosed(func() {
|
|||
// clock.stop = true
|
|||
//})
|
|||
|
|||
content := clock.render() |
|||
go clock.animate(content) |
|||
|
|||
listener := make(chan fyne.Settings) |
|||
fyne.CurrentApp().Settings().AddChangeListener(listener) |
|||
go func() { |
|||
for { |
|||
settings := <-listener |
|||
clock.applyTheme(settings) |
|||
} |
|||
}() |
|||
|
|||
return content |
|||
} |
@ -0,0 +1,123 @@ |
|||
package fractal |
|||
|
|||
import ( |
|||
"image/color" |
|||
"math" |
|||
|
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/canvas" |
|||
"fyne.io/fyne/v2/container" |
|||
"fyne.io/fyne/v2/theme" |
|||
) |
|||
|
|||
type fractal struct { |
|||
currIterations uint |
|||
currScale, currX, currY float64 |
|||
|
|||
window fyne.Window |
|||
canvas fyne.CanvasObject |
|||
} |
|||
|
|||
func (f *fractal) Layout(objects []fyne.CanvasObject, size fyne.Size) { |
|||
f.canvas.Resize(size) |
|||
} |
|||
|
|||
func (f *fractal) MinSize(objects []fyne.CanvasObject) fyne.Size { |
|||
return fyne.NewSize(320, 240) |
|||
} |
|||
|
|||
//lint:ignore U1000 See TODO inside the .Show() method.
|
|||
func (f *fractal) refresh() { |
|||
if f.currScale >= 1.0 { |
|||
f.currIterations = 100 |
|||
} else { |
|||
f.currIterations = uint(100 * (1 + math.Pow((math.Log10(1/f.currScale)), 1.25))) |
|||
} |
|||
|
|||
f.window.Canvas().Refresh(f.canvas) |
|||
} |
|||
|
|||
func (f *fractal) scaleChannel(c float64, start, end uint32) uint8 { |
|||
if end >= start { |
|||
return (uint8)(c*float64(uint8(end-start))) + uint8(start) |
|||
} |
|||
|
|||
return (uint8)((1-c)*float64(uint8(start-end))) + uint8(end) |
|||
} |
|||
|
|||
func (f *fractal) scaleColor(c float64, start, end color.Color) color.Color { |
|||
r1, g1, b1, _ := start.RGBA() |
|||
r2, g2, b2, _ := end.RGBA() |
|||
return color.RGBA{f.scaleChannel(c, r1, r2), f.scaleChannel(c, g1, g2), f.scaleChannel(c, b1, b2), 0xff} |
|||
} |
|||
|
|||
func (f *fractal) mandelbrot(px, py, w, h int) color.Color { |
|||
drawScale := 3.5 * f.currScale |
|||
aspect := (float64(h) / float64(w)) |
|||
cRe := ((float64(px)/float64(w))-0.5)*drawScale + f.currX |
|||
cIm := ((float64(py)/float64(w))-(0.5*aspect))*drawScale - f.currY |
|||
|
|||
var i uint |
|||
var x, y, xsq, ysq float64 |
|||
|
|||
for i = 0; i < f.currIterations && (xsq+ysq <= 4); i++ { |
|||
xNew := float64(xsq-ysq) + cRe |
|||
y = 2*x*y + cIm |
|||
x = xNew |
|||
|
|||
xsq = x * x |
|||
ysq = y * y |
|||
} |
|||
|
|||
if i == f.currIterations { |
|||
return theme.BackgroundColor() |
|||
} |
|||
|
|||
mu := (float64(i) / float64(f.currIterations)) |
|||
c := math.Sin((mu / 2) * math.Pi) |
|||
|
|||
return f.scaleColor(c, theme.PrimaryColor(), theme.ForegroundColor()) |
|||
} |
|||
|
|||
//lint:ignore U1000 See TODO inside the .Show() method.
|
|||
func (f *fractal) fractalRune(r rune) { |
|||
if r == '+' { |
|||
f.currScale /= 1.1 |
|||
} else if r == '-' { |
|||
f.currScale *= 1.1 |
|||
} |
|||
|
|||
f.refresh() |
|||
} |
|||
|
|||
//lint:ignore U1000 See TODO inside the .Show() method.
|
|||
func (f *fractal) fractalKey(ev *fyne.KeyEvent) { |
|||
delta := f.currScale * 0.2 |
|||
if ev.Name == fyne.KeyUp { |
|||
f.currY -= delta |
|||
} else if ev.Name == fyne.KeyDown { |
|||
f.currY += delta |
|||
} else if ev.Name == fyne.KeyLeft { |
|||
f.currX += delta |
|||
} else if ev.Name == fyne.KeyRight { |
|||
f.currX -= delta |
|||
} |
|||
|
|||
f.refresh() |
|||
} |
|||
|
|||
// Show loads a Mandelbrot fractal example window for the specified app context
|
|||
func Show(win fyne.Window) fyne.CanvasObject { |
|||
fractal := &fractal{window: win} |
|||
fractal.canvas = canvas.NewRasterWithPixels(fractal.mandelbrot) |
|||
|
|||
fractal.currIterations = 100 |
|||
fractal.currScale = 1.0 |
|||
fractal.currX = -0.75 |
|||
fractal.currY = 0.0 |
|||
|
|||
return container.New(fractal, fractal.canvas) |
|||
// TODO: Register, and unregister, these keys:
|
|||
//window.Canvas().SetOnTypedRune(fractal.fractalRune)
|
|||
//window.Canvas().SetOnTypedKey(fractal.fractalKey)
|
|||
} |
@ -0,0 +1,10 @@ |
|||
module github.com/fyne-io/examples |
|||
|
|||
go 1.12 |
|||
|
|||
require ( |
|||
fyne.io/fyne/v2 v2.1.0 |
|||
github.com/stretchr/testify v1.6.1 |
|||
golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect |
|||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect |
|||
) |
@ -0,0 +1,93 @@ |
|||
fyne.io/fyne/v2 v2.1.0 h1:qzdkaXL/UpmMtG4FlsX9xMZ0Q93CRzLxkoiSXyplP/I= |
|||
fyne.io/fyne/v2 v2.1.0/go.mod h1:c1vwI38Ebd0dAdxVa6H1Pj6/+cK1xtDy61+I31g+s14= |
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
|||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= |
|||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= |
|||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= |
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= |
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA= |
|||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= |
|||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= |
|||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= |
|||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU= |
|||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= |
|||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= |
|||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= |
|||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= |
|||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= |
|||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= |
|||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= |
|||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= |
|||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= |
|||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= |
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
|||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
|||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= |
|||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= |
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= |
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= |
|||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
|||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
|||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= |
|||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= |
|||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= |
|||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= |
|||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= |
|||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= |
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= |
|||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= |
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= |
|||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= |
|||
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E= |
|||
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= |
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
|||
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= |
|||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
|||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= |
|||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= |
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= |
|||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= |
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
|||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= |
|||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= |
|||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= |
|||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 208 KiB |
@ -0,0 +1,14 @@ |
|||
#!/bin/sh |
|||
|
|||
DIR=`dirname "$0"` |
|||
FILE=bundled.go |
|||
BIN=`go env GOPATH`/bin |
|||
|
|||
cd $DIR |
|||
rm $FILE |
|||
|
|||
$BIN/fyne bundle -package icon -name bugBitmap bug.png > $FILE |
|||
$BIN/fyne bundle -package icon -append -name clockBitmap clock.png >> $FILE |
|||
$BIN/fyne bundle -package icon -append -name fractalBitmap fractal.png >> $FILE |
|||
$BIN/fyne bundle -package icon -append -name xkcdBitmap xkcd.png >> $FILE |
|||
|
@ -0,0 +1,13 @@ |
|||
package icon |
|||
|
|||
// BugBitmap an icon for the bug example
|
|||
var BugBitmap = bugBitmap |
|||
|
|||
// ClockBitmap an icon for the clock example
|
|||
var ClockBitmap = clockBitmap |
|||
|
|||
// FractalBitmap an icon for the fractal example
|
|||
var FractalBitmap = fractalBitmap |
|||
|
|||
// XKCDBitmap an icon for the XKCD example
|
|||
var XKCDBitmap = xkcdBitmap |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 496 KiB |
@ -0,0 +1,71 @@ |
|||
//go:generate fyne bundle -o data.go Icon.png
|
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/app" |
|||
"fyne.io/fyne/v2/canvas" |
|||
"fyne.io/fyne/v2/container" |
|||
"fyne.io/fyne/v2/theme" |
|||
"fyne.io/fyne/v2/widget" |
|||
|
|||
"github.com/fyne-io/examples/bugs" |
|||
"github.com/fyne-io/examples/clock" |
|||
"github.com/fyne-io/examples/fractal" |
|||
"github.com/fyne-io/examples/img/icon" |
|||
"github.com/fyne-io/examples/tictactoe" |
|||
"github.com/fyne-io/examples/xkcd" |
|||
) |
|||
|
|||
type appInfo struct { |
|||
name string |
|||
icon fyne.Resource |
|||
canv bool |
|||
run func(fyne.Window) fyne.CanvasObject |
|||
} |
|||
|
|||
var apps = []appInfo{ |
|||
{"Bugs", icon.BugBitmap, false, bugs.Show}, |
|||
{"XKCD", icon.XKCDBitmap, false, xkcd.Show}, |
|||
{"Clock", icon.ClockBitmap, true, clock.Show}, |
|||
{"Fractal", icon.FractalBitmap, true, fractal.Show}, |
|||
{"Tic Tac Toe", theme.RadioButtonIcon(), true, tictactoe.Show}, |
|||
} |
|||
|
|||
func main() { |
|||
a := app.New() |
|||
a.SetIcon(resourceIconPng) |
|||
|
|||
content := container.NewMax() |
|||
w := a.NewWindow("Examples") |
|||
|
|||
appList := widget.NewList( |
|||
func() int { |
|||
return len(apps) |
|||
}, |
|||
func() fyne.CanvasObject { |
|||
icon := &canvas.Image{} |
|||
label := widget.NewLabel("Text Editor") |
|||
labelHeight := label.MinSize().Height |
|||
icon.SetMinSize(fyne.NewSize(labelHeight, labelHeight)) |
|||
return container.NewBorder(nil, nil, icon, nil, |
|||
label) |
|||
}, |
|||
func(id widget.ListItemID, obj fyne.CanvasObject) { |
|||
img := obj.(*fyne.Container).Objects[1].(*canvas.Image) |
|||
text := obj.(*fyne.Container).Objects[0].(*widget.Label) |
|||
img.Resource = apps[id].icon |
|||
img.Refresh() |
|||
text.SetText(apps[id].name) |
|||
}) |
|||
appList.OnSelected = func(id widget.ListItemID) { |
|||
content.Objects = []fyne.CanvasObject{apps[id].run(w)} |
|||
} |
|||
|
|||
split := container.NewHSplit(appList, content) |
|||
split.Offset = 0.1 |
|||
w.SetContent(split) |
|||
w.Resize(fyne.NewSize(480, 360)) |
|||
w.ShowAndRun() |
|||
} |
@ -0,0 +1,101 @@ |
|||
package tictactoe |
|||
|
|||
import ( |
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/dialog" |
|||
"fyne.io/fyne/v2/theme" |
|||
"fyne.io/fyne/v2/widget" |
|||
) |
|||
|
|||
type board struct { |
|||
pieces [3][3]uint8 |
|||
turn uint8 |
|||
finished bool |
|||
} |
|||
|
|||
func (b *board) result() uint8 { |
|||
// Check for a win in the diagonal direction from top left to bottom right.
|
|||
if b.pieces[0][0] != 0 && b.pieces[0][0] == b.pieces[1][1] && b.pieces[1][1] == b.pieces[2][2] { |
|||
return b.pieces[0][0] |
|||
} |
|||
|
|||
// Check for a win in the diagonal direction from bottom left to top right.
|
|||
if b.pieces[0][2] != 0 && b.pieces[0][2] == b.pieces[1][1] && b.pieces[1][1] == b.pieces[2][0] { |
|||
return b.pieces[0][2] |
|||
} |
|||
|
|||
for i := range b.pieces { |
|||
// Check for a win in the horizontal direction.
|
|||
if b.pieces[i][0] != 0 && b.pieces[i][0] == b.pieces[i][1] && b.pieces[i][1] == b.pieces[i][2] { |
|||
return b.pieces[i][0] |
|||
} |
|||
|
|||
// Check for a win in the vertical direction.
|
|||
if b.pieces[0][i] != 0 && b.pieces[0][i] == b.pieces[1][i] && b.pieces[1][i] == b.pieces[2][i] { |
|||
return b.pieces[0][i] |
|||
} |
|||
} |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
func (b *board) newClick(row, column int) { |
|||
b.pieces[row][column] = b.turn%2 + 1 |
|||
|
|||
if b.turn > 3 && b.turn < 8 { |
|||
winner := b.result() |
|||
if winner == 0 { |
|||
return |
|||
} |
|||
|
|||
number := string(winner + 48) // Number 1 is ascii #49 and 2 is ascii #50.
|
|||
dialog.ShowInformation("Player "+number+" has won!", "Congratulations to player "+number+" for winning.", fyne.CurrentApp().Driver().AllWindows()[0]) |
|||
b.finished = true |
|||
} else if b.turn == 8 { |
|||
dialog.ShowInformation("It is a tie!", "Nobody has won. Better luck next time.", fyne.CurrentApp().Driver().AllWindows()[0]) |
|||
b.finished = true |
|||
} |
|||
} |
|||
|
|||
func (b *board) Reset() { |
|||
for i := range b.pieces { |
|||
b.pieces[i][0] = 0 |
|||
b.pieces[i][1] = 0 |
|||
b.pieces[i][2] = 0 |
|||
} |
|||
|
|||
b.finished = false |
|||
b.turn = 0 |
|||
} |
|||
|
|||
type boardIcon struct { |
|||
widget.Icon |
|||
board *board |
|||
row, column int |
|||
} |
|||
|
|||
func (i *boardIcon) Tapped(ev *fyne.PointEvent) { |
|||
if i.board.pieces[i.row][i.column] != 0 || i.board.finished { |
|||
return |
|||
} |
|||
|
|||
if i.board.turn%2 == 0 { |
|||
i.SetResource(theme.RadioButtonIcon()) |
|||
} else { |
|||
i.SetResource(theme.CancelIcon()) |
|||
} |
|||
|
|||
i.board.newClick(i.row, i.column) |
|||
i.board.turn++ |
|||
} |
|||
|
|||
func (i *boardIcon) Reset() { |
|||
i.SetResource(theme.ViewFullScreenIcon()) |
|||
} |
|||
|
|||
func newBoardIcon(row, column int, board *board) *boardIcon { |
|||
i := &boardIcon{board: board, row: row, column: column} |
|||
i.SetResource(theme.ViewFullScreenIcon()) |
|||
i.ExtendBaseWidget(i) |
|||
return i |
|||
} |
@ -0,0 +1,30 @@ |
|||
package tictactoe |
|||
|
|||
import ( |
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/container" |
|||
"fyne.io/fyne/v2/theme" |
|||
"fyne.io/fyne/v2/widget" |
|||
) |
|||
|
|||
// Show loads a tic-tac-toe example window for the specified app context
|
|||
func Show(win fyne.Window) fyne.CanvasObject { |
|||
board := &board{} |
|||
|
|||
grid := container.NewGridWithColumns(3) |
|||
for r := 0; r < 3; r++ { |
|||
for c := 0; c < 3; c++ { |
|||
grid.Add(newBoardIcon(r, c, board)) |
|||
} |
|||
} |
|||
|
|||
reset := widget.NewButtonWithIcon("Reset Board", theme.ViewRefreshIcon(), func() { |
|||
for i := range grid.Objects { |
|||
grid.Objects[i].(*boardIcon).Reset() |
|||
} |
|||
|
|||
board.Reset() |
|||
}) |
|||
|
|||
return container.NewBorder(reset, nil, nil, nil, grid) |
|||
} |
@ -0,0 +1,166 @@ |
|||
package xkcd |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"io" |
|||
"io/ioutil" |
|||
"log" |
|||
"math/rand" |
|||
"net/http" |
|||
"os" |
|||
"reflect" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"fyne.io/fyne/v2" |
|||
"fyne.io/fyne/v2/canvas" |
|||
"fyne.io/fyne/v2/container" |
|||
"fyne.io/fyne/v2/layout" |
|||
"fyne.io/fyne/v2/widget" |
|||
) |
|||
|
|||
// XKCD is an app to get xkcd images and display them
|
|||
type XKCD struct { |
|||
ID int `json:"num"` |
|||
Title string `json:"title"` |
|||
Day string `json:"day"` |
|||
Month string `json:"month"` |
|||
Year string `json:"year"` |
|||
Link string `json:"link"` |
|||
SafeTitle string `json:"safe_title"` |
|||
Transcript string `json:"transcript"` |
|||
News string `json:"news"` |
|||
Alt string `json:"alt"` |
|||
Img string `json:"img"` |
|||
|
|||
image *canvas.Image |
|||
iDEntry *widget.Entry |
|||
labels map[string]*widget.Label |
|||
} |
|||
|
|||
func (x *XKCD) newLabel(name string) *widget.Label { |
|||
w := widget.NewLabel("") |
|||
x.labels[name] = w |
|||
return w |
|||
} |
|||
|
|||
// NewXKCD returns a new xkcd app
|
|||
func NewXKCD() *XKCD { |
|||
rand.Seed(time.Now().UnixNano()) |
|||
return &XKCD{ |
|||
labels: make(map[string]*widget.Label), |
|||
} |
|||
} |
|||
|
|||
// Submit will lookup the xkcd cartoon and do something useful with it
|
|||
func (x *XKCD) Submit() { |
|||
// Get the ID
|
|||
id, _ := strconv.Atoi(x.iDEntry.Text) |
|||
if id == 0 { |
|||
id = rand.Intn(2075) |
|||
} |
|||
|
|||
resp, err := http.Get(fmt.Sprintf("https://xkcd.com/%d/info.0.json", id)) |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer resp.Body.Close() |
|||
if resp.StatusCode == http.StatusOK { |
|||
data, _ := ioutil.ReadAll(resp.Body) |
|||
json.Unmarshal(data, x) |
|||
x.DataToScreen() |
|||
} else { |
|||
fmt.Println("Error getting ID", id, resp.Status, resp.StatusCode) |
|||
} |
|||
} |
|||
|
|||
func (x *XKCD) downloadImage(url string) { |
|||
response, e := http.Get(url) |
|||
if e != nil { |
|||
log.Fatal(e) |
|||
} |
|||
defer response.Body.Close() |
|||
|
|||
file, err := ioutil.TempFile(os.TempDir(), "xkcd.png") |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
defer file.Close() |
|||
|
|||
_, err = io.Copy(file, response.Body) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
|
|||
x.image.File = file.Name() |
|||
canvas.Refresh(x.image) |
|||
} |
|||
|
|||
// DataToScreen copies the data model to the screen
|
|||
func (x *XKCD) DataToScreen() { |
|||
myType := reflect.TypeOf(x).Elem() |
|||
myValue := reflect.ValueOf(x).Elem() |
|||
for i := 0; i < myType.NumField(); i++ { |
|||
tag := myType.Field(i).Tag.Get("json") |
|||
switch tag { |
|||
case "": // not a display field
|
|||
case "img": // special field for images
|
|||
url := myValue.Field(i).String() |
|||
|
|||
go x.downloadImage(url) |
|||
case "num": |
|||
v := myValue.Field(i).Int() |
|||
x.iDEntry.SetText(fmt.Sprintf("%d", v)) |
|||
default: |
|||
v := myValue.Field(i).String() |
|||
if newline := strings.IndexAny(v, "\n.-,"); newline > -1 { |
|||
v = v[:newline] + "..." |
|||
} |
|||
x.labels[tag].SetText(v) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// NewForm generates a new XKCD form
|
|||
func (x *XKCD) NewForm(w fyne.Window) fyne.Widget { |
|||
form := &widget.Form{} |
|||
tt := reflect.TypeOf(x).Elem() |
|||
for i := 0; i < tt.NumField(); i++ { |
|||
fld := tt.Field(i) |
|||
tag := fld.Tag.Get("json") |
|||
switch tag { |
|||
case "": // not a display field
|
|||
case "img": // special field for images
|
|||
// we created this in the setup
|
|||
case "num": // special field for ID
|
|||
entry := widget.NewEntry() |
|||
x.iDEntry = entry |
|||
form.Append(fld.Name, entry) |
|||
default: |
|||
form.Append(fld.Name, x.newLabel(tag)) |
|||
} |
|||
} |
|||
return form |
|||
} |
|||
|
|||
// Show starts a new xkcd widget
|
|||
func Show(win fyne.Window) fyne.CanvasObject { |
|||
x := NewXKCD() |
|||
|
|||
form := x.NewForm(win) |
|||
submit := widget.NewButton("Submit", func() { |
|||
x.Submit() |
|||
}) |
|||
submit.Importance = widget.HighImportance |
|||
buttons := container.NewHBox( |
|||
layout.NewSpacer(), |
|||
widget.NewButton("Random", func() { |
|||
x.iDEntry.Text = "" |
|||
x.Submit() |
|||
}), |
|||
submit) |
|||
x.image = &canvas.Image{FillMode: canvas.ImageFillOriginal} |
|||
return container.NewBorder(form, buttons, nil, nil, x.image) |
|||
} |