go 使用fyne实现桌面程序的计算器例子

使用Fyne工具包构建跨平台应用是非常简单的,在此之前我们需要做一些准备功能做,比如安装一些gcc基础图形依赖库,还有go语言本身的运行开发环境都是必要的。

在此之前我们希望你是go语言的已入门用户,掌握go的协程,管道,以及大多数语法,这样会让你学习起来更轻松

下面是实现的代码,有一部分还没有实现,如果需要可以继续研究.

Go 复制代码
package main

import (
	"image/color"
	"math"
	"strconv"
	"strings"
	"time"

	"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"
	"fyne.io/fyne/v2/driver/desktop"
)

// 自定义按钮样式
type calculatorButton struct {
	widget.Button
	bgColor     color.Color
	pressScale  float32    // 按下时的缩放比例
	isPressed   bool       // 按钮是否被按下
	animating   bool       // 是否正在动画中
}

func newCalculatorButton(label string, bgColor color.Color, tapped func()) *calculatorButton {
	button := &calculatorButton{
		bgColor:    bgColor,
		pressScale: 0.95,    // 按下时缩小到95%
		isPressed:  false,
		animating:  false,
	}
	button.ExtendBaseWidget(button)
	button.Text = label
	button.OnTapped = tapped
	return button
}

// 添加鼠标按下事件处理
func (b *calculatorButton) MouseDown(*desktop.MouseEvent) {
	b.isPressed = true
	b.animating = true
	b.Refresh()
}

// 添加鼠标释放事件处理
func (b *calculatorButton) MouseUp(*desktop.MouseEvent) {
	b.isPressed = false
	b.animating = true
	b.Refresh()
}

func (b *calculatorButton) CreateRenderer() fyne.WidgetRenderer {
	background := &canvas.Circle{
		FillColor: b.bgColor,
	}
	
	text := canvas.NewText(b.Text, color.White)
	text.TextSize = 20
	text.Alignment = fyne.TextAlignCenter

	renderer := &calculatorButtonRenderer{
		button:     b,
		background: background,
		text:       text,
		objects:    []fyne.CanvasObject{background, text},
	}

	// 创建动画
	renderer.animation = fyne.NewAnimation(
		time.Millisecond*100,    // 动画持续100毫秒
		func(progress float32) {
			if b.isPressed {
				// 从1.0缩pressScale
				scale := 1.0 - (1.0-b.pressScale)*progress
				renderer.updateScale(scale)
			} else {
				// 从pressScale恢复到1.0
				scale := b.pressScale + (1.0-b.pressScale)*progress
				renderer.updateScale(scale)
			}
			renderer.Refresh()
		},
	)

	return renderer
}

type calculatorButtonRenderer struct {
	button     *calculatorButton
	background *canvas.Circle
	text       *canvas.Text
	objects    []fyne.CanvasObject
	animation  *fyne.Animation    // 添加动画对象
}

// 添加缩放更新函数
func (r *calculatorButtonRenderer) updateScale(scale float32) {
	baseSize := fyne.NewSize(60, 45)  // 使用固定的基础大小
	scaledSize := fyne.NewSize(
		float32(baseSize.Width)*scale,
		float32(baseSize.Height)*scale,
	)
	
	// 计算偏移以保持按钮居中
	offset := fyne.NewPos(
		(float32(baseSize.Width)-scaledSize.Width)/2,
		(float32(baseSize.Height)-scaledSize.Height)/2,
	)
	
	r.background.Resize(scaledSize)
	r.background.Move(offset)
	
	// 更新文本位置
	textSize := r.text.MinSize()
	r.text.Resize(textSize)
	r.text.Move(fyne.NewPos(
		(baseSize.Width-textSize.Width)/2,
		(baseSize.Height-textSize.Height)/2,
	))
}

// 修改 Refresh 函数
func (r *calculatorButtonRenderer) Refresh() {
	if r.button.animating {
		r.animation.Start()
		r.button.animating = false
	}
	
	r.background.FillColor = r.button.bgColor
	r.background.Refresh()
	r.text.Text = r.button.Text
	r.text.Refresh()
}

// 修改 Destroy 函数
func (r *calculatorButtonRenderer) Destroy() {
	r.animation.Stop()
}

// 添加Layout方法
func (r *calculatorButtonRenderer) Layout(size fyne.Size) {
	// 设置背景大小和位置
	r.background.Resize(size)
	
	// 文本居中
	textSize := r.text.MinSize()
	r.text.Resize(textSize)
	r.text.Move(fyne.NewPos(
		(size.Width-textSize.Width)/2,
		(size.Height-textSize.Height)/2,
	))
}

// 添加MinSize方法
func (r *calculatorButtonRenderer) MinSize() fyne.Size {
	return fyne.NewSize(60, 45)
}

// 添加Objects方法
func (r *calculatorButtonRenderer) Objects() []fyne.CanvasObject {
	return r.objects
}

// 添加自定义显示组件
type calculatorDisplay struct {
	widget.BaseWidget
	text string
	process string  // 添加计算过程
}

func newCalculatorDisplay() *calculatorDisplay {
	display := &calculatorDisplay{text: "0", process: ""}
	display.ExtendBaseWidget(display)
	return display
}

func (d *calculatorDisplay) CreateRenderer() fyne.WidgetRenderer {
	result := canvas.NewText(d.text, color.White)
	result.TextSize = 32
	result.Alignment = fyne.TextAlignTrailing

	process := canvas.NewText(d.process, color.Gray{Y: 180})  // 灰色显示计算过程
	process.TextSize = 20
	process.Alignment = fyne.TextAlignTrailing

	return &calculatorDisplayRenderer{
		display: d,
		result:  result,
		process: process,
		objects: []fyne.CanvasObject{result, process},
	}
}

type calculatorDisplayRenderer struct {
	display *calculatorDisplay
	result  *canvas.Text
	process *canvas.Text
	objects []fyne.CanvasObject
}

func (r *calculatorDisplayRenderer) MinSize() fyne.Size {
	return fyne.NewSize(280, 70)  // 增加高度以容纳两行文本
}

func (r *calculatorDisplayRenderer) Layout(size fyne.Size) {
	processHeight := float32(25)
	r.process.Resize(fyne.NewSize(size.Width, processHeight))
	r.process.Move(fyne.NewPos(0, 0))
	
	r.result.Resize(fyne.NewSize(size.Width, size.Height-processHeight))
	r.result.Move(fyne.NewPos(0, processHeight))
}

func (r *calculatorDisplayRenderer) Refresh() {
	r.result.Text = r.display.text
	r.process.Text = r.display.process
	r.result.Refresh()
	r.process.Refresh()
}

func (r *calculatorDisplayRenderer) Objects() []fyne.CanvasObject {
	return r.objects
}

func (r *calculatorDisplayRenderer) Destroy() {}

func main() {
	a := app.New()
	w := a.NewWindow("计算器")
	w.SetFixedSize(true)

	// 设置深色主题
	a.Settings().SetTheme(theme.DarkTheme())

	// 声明计算器状态变量
	var (
		firstNumber float64
		operation   string
		newNumber   = true
		showingResult = false  // 添加这个变量
	)

	// 使用新的显示组件替代原来的Entry
	display := newCalculatorDisplay()

	// 定义颜色
	grayColor := &color.NRGBA{R: 51, G: 51, B: 51, A: 255}
	orangeColor := &color.NRGBA{R: 255, G: 159, B: 10, A: 255}
	lightGrayColor := &color.NRGBA{R: 165, G: 165, B: 165, A: 255}
	darkerGrayColor := &color.NRGBA{R: 40, G: 40, B: 40, A: 255}

	// 数字按钮
	numbers := map[string]*calculatorButton{}
	for i := 0; i <= 9; i++ {
		number := i
		numbers[strconv.Itoa(i)] = newCalculatorButton(strconv.Itoa(i), grayColor, func() {
			if newNumber || showingResult {
				display.text = ""
				if showingResult {
					display.process = ""  // 清除之前的计算过程
				}
				newNumber = false
				showingResult = false
			}
			display.text += strconv.Itoa(number)
			display.Refresh()
		})
	}

	// 运算符按钮
	add := newCalculatorButton("+", orangeColor, func() {
		if showingResult {
			firstNumber, _ = strconv.ParseFloat(display.text, 64)
			display.process = display.text + " +"  // 开始新的计算过程
			showingResult = false
		} else {
			if !newNumber {
				secondNumber, _ := strconv.ParseFloat(display.text, 64)
				firstNumber = calculateResult(firstNumber, secondNumber, operation)
				display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
				display.process = display.text + " +"  // 更新计算过程
			} else {
				firstNumber, _ = strconv.ParseFloat(display.text, 64)
				display.process = display.text + " +"  // 显示第一个数字和运算符
			}
		}
		operation = "+"
		newNumber = true
		display.Refresh()
	})

	subtract := newCalculatorButton("-", orangeColor, func() {
		if showingResult {
			firstNumber, _ = strconv.ParseFloat(display.text, 64)
			showingResult = false
		} else {
			if !newNumber {
				secondNumber, _ := strconv.ParseFloat(display.text, 64)
				firstNumber = calculateResult(firstNumber, secondNumber, operation)
				display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
			} else {
				firstNumber, _ = strconv.ParseFloat(display.text, 64)
			}
		}
		operation = "-"
		newNumber = true
		display.Refresh()
	})

	multiply := newCalculatorButton("×", orangeColor, func() {
		if showingResult {
			firstNumber, _ = strconv.ParseFloat(display.text, 64)
			showingResult = false
		} else {
			if !newNumber {
				secondNumber, _ := strconv.ParseFloat(display.text, 64)
				firstNumber = calculateResult(firstNumber, secondNumber, operation)
				display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
			} else {
				firstNumber, _ = strconv.ParseFloat(display.text, 64)
			}
		}
		operation = "*"
		newNumber = true
		display.Refresh()
	})

	divide := newCalculatorButton("÷", orangeColor, func() {
		if showingResult {
			firstNumber, _ = strconv.ParseFloat(display.text, 64)
			showingResult = false
		} else {
			if !newNumber {
				secondNumber, _ := strconv.ParseFloat(display.text, 64)
				firstNumber = calculateResult(firstNumber, secondNumber, operation)
				display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
			} else {
				firstNumber, _ = strconv.ParseFloat(display.text, 64)
			}
		}
		operation = "/"
		newNumber = true
		display.Refresh()
	})

	equals := newCalculatorButton("=", orangeColor, func() {
		if !newNumber {
			secondNumber, _ := strconv.ParseFloat(display.text, 64)
			display.process += " " + display.text + " ="  // 完成计算过程
			result := calculateResult(firstNumber, secondNumber, operation)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			firstNumber = result
			showingResult = true
			display.Refresh()
		}
	})

	// 额外的功能按钮
	ac := newCalculatorButton("AC", lightGrayColor, func() {
		display.text = "0"
		display.process = ""  // 清除计算过程
		firstNumber = 0
		operation = ""
		newNumber = true
		showingResult = false
		display.Refresh()
	})

	plusMinus := newCalculatorButton("+/-", lightGrayColor, func() {
		if current, err := strconv.ParseFloat(display.text, 64); err == nil {
			display.text = strconv.FormatFloat(-current, 'f', -1, 64)
			display.Refresh()
		}
	})

	percent := newCalculatorButton("%", lightGrayColor, func() {
		if current, err := strconv.ParseFloat(display.text, 64); err == nil {
			display.text = strconv.FormatFloat(current/100, 'f', -1, 64)
			display.Refresh()
		}
	})

	decimal := newCalculatorButton(".", grayColor, func() {
		if !strings.Contains(display.text, ".") {
			display.text += "."
			display.Refresh()
		}
	})

	// 科学计算器的额外功能按钮
	mc := newCalculatorButton("mc", darkerGrayColor, func() {
		// 内存清除功能
	})

	mPlus := newCalculatorButton("m+", darkerGrayColor, func() {
		// 内存加功能
	})

	mMinus := newCalculatorButton("m-", darkerGrayColor, func() {
		// 内存减功能
	})

	mr := newCalculatorButton("mr", darkerGrayColor, func() {
		// 内存调用功能
	})

	// 科学运算按钮
	sin := newCalculatorButton("sin", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Sin(val * math.Pi / 180) // 转换为弧度
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	cos := newCalculatorButton("cos", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Cos(val * math.Pi / 180)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	tan := newCalculatorButton("tan", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Tan(val * math.Pi / 180)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	ln := newCalculatorButton("ln", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Log(val)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	log := newCalculatorButton("log₁₀", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Log10(val)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	sqrt := newCalculatorButton("√x", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Sqrt(val)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	square := newCalculatorButton("x²", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			result := math.Pow(val, 2)
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	pi := newCalculatorButton("π", darkerGrayColor, func() {
		display.text = strconv.FormatFloat(math.Pi, 'f', -1, 64)
		display.Refresh()
	})

	e := newCalculatorButton("e", darkerGrayColor, func() {
		display.text = strconv.FormatFloat(math.E, 'f', -1, 64)
		display.Refresh()
	})

	factorial := newCalculatorButton("x!", darkerGrayColor, func() {
		if val, err := strconv.ParseFloat(display.text, 64); err == nil {
			n := int(val)
			result := 1.0
			for i := 2; i <= n; i++ {
				result *= float64(i)
			}
			display.text = strconv.FormatFloat(result, 'f', -1, 64)
			display.Refresh()
		}
	})

	// 添加模式切换状态
	var isScientificMode = false
	
	// 声明updateLayout变量(在最前面)
	var updateLayout func()
	
	// 创建主容器
	mainContainer := container.NewVBox()
	
	// 创建显示区域容器
	displayContainer := container.NewPadded(display)
	
	// 创建基本计算器布局
	createBasicLayout := func() *fyne.Container {
		return container.NewGridWithColumns(4,
			ac, plusMinus, percent, divide,
			numbers["7"], numbers["8"], numbers["9"], multiply,
			numbers["4"], numbers["5"], numbers["6"], subtract,
			numbers["1"], numbers["2"], numbers["3"], add,
			numbers["0"], decimal, equals,
		)
	}

	// 创建科学计算器布局
	createScientificLayout := func() *fyne.Container {
		return container.NewGridWithColumns(10,
			// 第一行
			newCalculatorButton("(", darkerGrayColor, nil),
			newCalculatorButton(")", darkerGrayColor, nil),
			mc, mPlus, mMinus, mr,
			ac, plusMinus, percent, divide,
			// 第二行
			newCalculatorButton("2ⁿᵈ", darkerGrayColor, nil),
			square,
			newCalculatorButton("x³", darkerGrayColor, nil),
			newCalculatorButton("xʸ", darkerGrayColor, nil),
			e,  // 使用 e 按钮
			newCalculatorButton("10ˣ", darkerGrayColor, nil),
			numbers["7"], numbers["8"], numbers["9"], multiply,
			// 第三行
			factorial,  // 使用阶乘按钮
			sqrt,      // 使用平方根按钮
			pi,        // 使用 π 按钮
			ln,        // 使用自然对数按钮
			log,       // 使用常用对数按钮
			newCalculatorButton("EE", darkerGrayColor, nil),
			numbers["4"], numbers["5"], numbers["6"], subtract,
			// 第四行
			sin,       // 使用正弦按钮
			cos,       // 使用余弦按钮
			tan,       // 使用正切按钮
			newCalculatorButton("sinh", darkerGrayColor, nil),
			newCalculatorButton("cosh", darkerGrayColor, nil),
			newCalculatorButton("tanh", darkerGrayColor, nil),
			numbers["1"], numbers["2"], numbers["3"], add,
			// 第五行
			newCalculatorButton("Rad", darkerGrayColor, nil),
			newCalculatorButton("sinh⁻¹", darkerGrayColor, nil),
			newCalculatorButton("cosh⁻¹", darkerGrayColor, nil),
			newCalculatorButton("tanh⁻¹", darkerGrayColor, nil),
			newCalculatorButton("Rand", darkerGrayColor, nil),
			newCalculatorButton("EE", darkerGrayColor, nil),
			numbers["0"], decimal, equals,
		)
	}
	
	// 创建顶部工具栏(不包含modeSwitch)
	toolbar := container.NewHBox(
		widget.NewSeparator(),
	)

	// 定义updateLayout函数
	updateLayout = func() {
		var layout fyne.CanvasObject
		if isScientificMode {
			layout = createScientificLayout()
		} else {
			layout = createBasicLayout()
		}
		
		mainContainer.Objects = []fyne.CanvasObject{
			toolbar,
			displayContainer,
			layout,
		}
		mainContainer.Refresh()
	}

	// 现在定义modeSwitch(在updateLayout之后)
	modeSwitch := newCalculatorButton("⚙", lightGrayColor, func() {
		isScientificMode = !isScientificMode
		
		startSize := w.Canvas().Size()
		var targetWidth float32
		if isScientificMode {
			targetWidth = 600
		} else {
			targetWidth = 300
		}
		targetSize := fyne.NewSize(targetWidth, 450)
		
		animation := fyne.NewAnimation(
			time.Millisecond*300,
			func(progress float32) {
				width := startSize.Width + (targetSize.Width-startSize.Width)*progress
				height := startSize.Height + (targetSize.Height-startSize.Height)*progress
				w.Resize(fyne.NewSize(width, height))
			},
		)
		
		animation.Start()
		updateLayout()
	})

	// 更新toolbar,添加modeSwitch
	toolbar.Objects = append([]fyne.CanvasObject{modeSwitch}, toolbar.Objects...)

	// 初始化布局
	updateLayout()

	// 直接使用mainContainer作为窗口内容
	w.SetContent(mainContainer)
	w.Resize(fyne.NewSize(300, 450))
	w.ShowAndRun()
}

// 添加一个辅助函数来计算结果
func calculateResult(first, second float64, op string) float64 {
	var result float64
	switch op {
	case "+":
		result = first + second
	case "-":
		result = first - second
	case "*":
		result = first * second
	case "/":
		if second != 0 {
			result = first / second
		}
	}
	return result
}

go.mod文件

Go 复制代码
module calc

go 1.23.1

require fyne.io/fyne/v2 v2.5.2

require (
	fyne.io/systray v1.11.0 // indirect
	github.com/BurntSushi/toml v1.4.0 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/fredbi/uri v1.1.0 // indirect
	github.com/fsnotify/fsnotify v1.7.0 // indirect
	github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
	github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect
	github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
	github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
	github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
	github.com/go-text/render v0.2.0 // indirect
	github.com/go-text/typesetting v0.2.0 // indirect
	github.com/godbus/dbus/v5 v5.1.0 // indirect
	github.com/gopherjs/gopherjs v1.17.2 // indirect
	github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
	github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
	github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/rymdport/portal v0.2.6 // indirect
	github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
	github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
	github.com/stretchr/testify v1.8.4 // indirect
	github.com/yuin/goldmark v1.7.1 // indirect
	golang.org/x/image v0.18.0 // indirect
	golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
	golang.org/x/net v0.25.0 // indirect
	golang.org/x/sys v0.20.0 // indirect
	golang.org/x/text v0.16.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)
相关推荐
Ajiang282473530411 分钟前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
幽兰的天空16 分钟前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024065 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic6 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it6 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康6 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
qq_17448285756 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
转世成为计算机大神6 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式