@ -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) |
||||
|
} |