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
)
相关推荐
长栎35 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode39 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812243 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode44 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战1 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha1 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn1 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改1 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构