处理动画

This commit is contained in:
2026-02-09 04:07:32 +08:00
parent 85ed0946c8
commit 9822cf901d
5 changed files with 383 additions and 56 deletions

View File

@@ -14,10 +14,55 @@ var PRESET_COLORS = []struct {
{"黄色", 0xFF, 0xFF, 0x00},
{"橙色", 0xFF, 0xA5, 0x00},
{"紫色", 0x80, 0x00, 0x80},
{"自定义", 0x00, 0x00, 0x00},
//{"熄灭", 0x00, 0x00, 0x00},
}
// PRESET_MULTI_COLORS 预设颜色
var PRESET_MULTI_COLORS = []struct {
Name string
Color1 []byte
Color2 []byte
}{
// 霓虹紫蓝 (非常电竞范)
{"霓虹紫蓝",
[]byte{0xFF, 0x00, 0xFF}, // 紫色 (Magenta)
[]byte{0x00, 0xFF, 0xFF}, // 青蓝色 (Cyan)
},
// 冰火 (经典红蓝对比)
{"冰火",
[]byte{0xFF, 0x00, 0x00}, // 纯红
[]byte{0x00, 0x00, 0xFF}, // 纯蓝
},
// 赛博朋克 (经典配色)
{"赛博粉黄",
[]byte{0xFF, 0x00, 0x7F}, // 玫粉色
[]byte{0xFF, 0xD7, 0x00}, // 金黄色
},
// 森林气息 (柔和过渡)
{"森翠",
[]byte{0x00, 0xFF, 0x00}, // 绿色
[]byte{0x00, 0xFF, 0x7F}, // 春绿
},
// 晚霞 (暖色调)
{"晚霞",
[]byte{0xFF, 0x45, 0x00}, // 橙红
[]byte{0xFF, 0x14, 0x93}, // 深粉
},
// 黑客帝国 (极简)
{"矩阵",
[]byte{0x00, 0xFF, 0x00}, // 翠绿
[]byte{0x00, 0x33, 0x00}, // 暗绿 (深邃感)
},
//{"熄灭", 0x00, 0x00, 0x00},
}
// 雷蛇绿 (标志性的 Razer Green)
//razerGreen := []byte{0x00, 0xFF, 0x00} // 或者稍微深一点的 0x44, 0xFF, 0x00
@@ -85,16 +130,18 @@ func (rd *RazerDevice) SetOffLightsColor(color []byte) {
}
// SetBreathingColor 设置双色呼吸
func (rd *RazerDevice) SetBreathingColor() {
func (rd *RazerDevice) SetBreathingColor(subMode byte, rgb1 []byte, rgb2 []byte, speed byte) {
// 1. 定义呼吸子模式
subMode := byte(0x02) // 0x02 为双色切换呼吸
rgb1 := []byte{0xFF, 0x00, 0x00} // 颜色1: 红色
rgb2 := []byte{0x00, 0x00, 0xFF} // 颜色2: 蓝色
speed := byte(0x01) // 速度: 1(快) - 3(慢), 200可能过大建议先用1测试
//subMode := byte(0x02) // 0x02 为双色切换呼吸
//rgb1 := []byte{0xFF, 0x00, 0x00} // 颜色1: 红色
//rgb2 := []byte{0x00, 0x00, 0xFF} // 颜色2: 蓝色
//speed := byte(0x01) // 速度: 1(快) - 3(慢), 200可能过大建议先用1测试
//fmt.Println(subMode)
// 2. 构造参数包 (总长 8 字节)
// 结构: [SubMode, R1, G1, B1, R2, G2, B2, Speed]
breathingParams := append([]byte{subMode}, append(rgb1, append(rgb2, speed)...)...)
fmt.Println(breathingParams)
// 3. 调用新版 buildArguments (内部不再强制加 0x01)
args := buildArguments(KBD_EFFECT_BREATHING, KBD_BACKLIGHT_LED, breathingParams)

View File

@@ -1,5 +1,11 @@
package utils
import (
"fmt"
"github.com/progrium/darwinkit/macos/appkit"
)
// --- 硬件识别常量 ---
// RAZER_VID (Vendor ID) 是雷蛇在 USB 协会注册的厂商唯一识别码
@@ -60,3 +66,32 @@ const KBD_BACKLIGHT_LED = 0x05 // 键盘背光灯光控制索引
const KBD_CMD_CLASS = 0x0F // 键盘指令分类 (Command Class)
const KBD_CMD_ID = 0x02 // 键盘指令标识 (Command ID)
const KBD_DATA_SIZE = 9 // 键盘数据包的有效负载长度
// 全局变量,追踪当前色盘控制的设备
var (
activeDeviceKey string
colorPanel appkit.ColorPanel
)
func initColorPanel() {
// 修复unsafe.Pointer 与 nil 比较
if colorPanel.Ptr() == nil {
colorPanel = appkit.ColorPanel_SharedColorPanel()
colorPanel.SetContinuous(true)
}
}
func openColorPicker() {
// 1. 获取共享的色盘实例(推荐做法)
colorPanel := appkit.ColorPanel_SharedColorPanel()
// 2. 配置 Target/Action (可选)
// 注意:在 Go 中处理 Selector 回调比较复杂,通常需要注册一个特定的 Class
// 3. 激活应用并显示色盘
// 如果你的程序不是 GUI 模式,色盘可能会隐藏在其他窗口后面
appkit.Application_SharedApplication().ActivateIgnoringOtherApps(true)
colorPanel.MakeKeyAndOrderFront(nil)
fmt.Println("Color Picker 已经尝试打开")
}

View File

@@ -7,13 +7,105 @@ import (
"github.com/progrium/darwinkit/objc"
)
var deviceStateCache = make(map[string]State)
var Devs map[string]*RazerDevice = make(map[string]*RazerDevice)
type State struct {
Effect int
Color string
var Temp = TempData{}
type TempData struct {
TempKey string
TempState State
}
var DeviceStateCache = make(map[string]State)
type State interface {
GetEffect() int
GetColorKey() string
GetMultiColorKey() string
GetModel() int
SetColor([]byte)
GetColor() []byte
GetColor1() []byte
GetColor2() []byte
GetSpeed() int
}
// StaticState 常亮模式
type StaticState struct {
Effect int
ColorKey string
Color []byte
}
func (s *StaticState) GetEffect() int {
return s.Effect
}
func (s *StaticState) GetModel() int {
return 0
}
func (s *StaticState) GetColorKey() string {
return s.ColorKey
}
func (s *StaticState) GetMultiColorKey() string {
return ""
}
func (s *StaticState) SetColor(color []byte) {
s.Color = color
}
func (s *StaticState) GetColor() []byte {
return s.Color
}
func (s *StaticState) GetColor1() []byte {
return []byte{}
}
func (s *StaticState) GetColor2() []byte {
return []byte{}
}
func (s *StaticState) GetSpeed() int {
return 0
}
// BreathingState 呼吸模式
type BreathingState struct {
Effect int
Model int // 单色呼吸 双色呼吸 随机颜色
Speed int // 快 中 慢
Color []byte
ColorKey string
Color1 []byte // 多色
Color2 []byte // 多色
MultiColorKey string // 多色
}
func (b *BreathingState) GetEffect() int {
return b.Effect
}
func (b *BreathingState) GetColorKey() string {
return b.ColorKey
}
func (b *BreathingState) GetModel() int {
return b.Model
}
func (b *BreathingState) GetMultiColorKey() string {
return b.MultiColorKey
}
func (b *BreathingState) SetColor(color []byte) {
b.Color = color
}
func (b *BreathingState) GetColor() []byte {
return b.Color
}
func (b *BreathingState) GetSpeed() int {
return b.Speed
}
func (b *BreathingState) GetColor1() []byte {
return b.Color1
}
func (b *BreathingState) GetColor2() []byte {
return b.Color2
}
func UpdateMenu(menu appkit.Menu, app appkit.Application, statusBarItem appkit.StatusItem) {
menu.RemoveAllItems()
@@ -25,7 +117,7 @@ func UpdateMenu(menu appkit.Menu, app appkit.Application, statusBarItem appkit.S
menu.AddItem(appkit.MenuItem_SeparatorItem())
devices := ScanDevices()
Devs = devices
// 更新 ToolTip 显示当前连接数
statusBarItem.Button().SetToolTip(fmt.Sprintf("雷蛇控制器 - 已连接设备: %d", len(devices)))
@@ -35,6 +127,9 @@ func UpdateMenu(menu appkit.Menu, app appkit.Application, statusBarItem appkit.S
menu.AddItem(none)
} else {
for _, dev := range devices {
key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
stateVal := DeviceStateCache[key]
// 设备标题 (不可点击)
devTitle := appkit.NewMenuItemWithAction("⌨️ "+dev.Name, "", func(sender objc.Object) {})
devTitle.SetEnabled(false)
@@ -53,14 +148,21 @@ func UpdateMenu(menu appkit.Menu, app appkit.Application, statusBarItem appkit.S
colorMenu := appkit.NewMenuWithTitle("Colors")
for _, c := range PRESET_COLORS {
colorItem := appkit.NewMenuItemWithAction(c.Name, "", func(sender objc.Object) {
//dev.ApplyEffect(KBD_EFFECT_STATIC, c.R, c.G, c.B)
dev.SetStaticColor([]byte{c.R, c.G, c.B})
// 更新状态与缓存
key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
deviceStateCache[key] = State{Effect: KBD_EFFECT_STATIC, Color: c.Name}
if c.Name == "自定义" {
DeviceStateCache[key] = &StaticState{Effect: KBD_EFFECT_STATIC, ColorKey: c.Name}
Temp = TempData{TempKey: key, TempState: DeviceStateCache[key]} // [key] = StaticState{Effect: KBD_EFFECT_STATIC, ColorKey: c.Name}
openColorPicker()
} else {
//dev.ApplyEffect(KBD_EFFECT_STATIC, c.R, c.G, c.B)
dev.SetStaticColor([]byte{c.R, c.G, c.B})
// 更新状态与缓存
DeviceStateCache[key] = &StaticState{Effect: KBD_EFFECT_STATIC, ColorKey: c.Name, Color: []byte{c.R, c.G, c.B}}
}
UpdateMenu(menu, app, statusBarItem)
})
if dev.CurrentEffect == KBD_EFFECT_STATIC && dev.CurrentColor == c.Name {
stateVal := DeviceStateCache[key]
if stateVal != nil && dev.CurrentEffect == KBD_EFFECT_STATIC && stateVal.GetColorKey() == c.Name {
colorItem.SetState(appkit.ControlStateValueOn)
}
colorMenu.AddItem(colorItem)
@@ -69,52 +171,123 @@ func UpdateMenu(menu appkit.Menu, app appkit.Application, statusBarItem appkit.S
menu.AddItem(staticItem)
// --- B. 呼吸模式 ---
breathItem := appkit.NewMenuItemWithAction("🌬️ 呼吸模式", "", func(sender objc.Object) {
dev.SetBreathingColor() // 默认绿色
key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
deviceStateCache[key] = State{Effect: KBD_EFFECT_BREATHING, Color: ""}
UpdateMenu(menu, app, statusBarItem)
})
breathItem := appkit.NewMenuItemWithAction("🌬️ 呼吸模式", "", func(sender objc.Object) {})
if dev.CurrentEffect == KBD_EFFECT_BREATHING {
breathItem.SetState(appkit.ControlStateValueOn)
}
breathRootMenu := appkit.NewMenuWithTitle("BreathingRoot")
singleItem := appkit.NewMenuItemWithAction("单色模式", "", func(sender objc.Object) {})
singleSub := appkit.NewMenuWithTitle("Single")
for _, color := range PRESET_COLORS {
item := appkit.NewMenuItemWithAction(color.Name, "", func(sender objc.Object) {
if color.Name == "自定义" {
DeviceStateCache[key] = &StaticState{Effect: KBD_EFFECT_BREATHING, ColorKey: color.Name}
Temp = TempData{TempKey: key, TempState: DeviceStateCache[key]} // [key] = StaticState{Effect: KBD_EFFECT_STATIC, ColorKey: c.Name}
openColorPicker()
} else {
if stateVal != nil {
dev.SetBreathingColor(0x01, []byte{color.R, color.G, color.B}, []byte{}, byte(stateVal.GetSpeed()))
} else {
dev.SetBreathingColor(0x01, []byte{color.R, color.G, color.B}, []byte{}, byte(1))
}
}
//dev.SetBreathing(0x01, color.R, color.G, color.B, 0, 0, 0, curState.Speed)
// 0x01: 单色 (只看第一组 RGB)
// 0x02: 双色 (看两组 RGB)
// 0x03: 随机
DeviceStateCache[key] = &BreathingState{Effect: KBD_EFFECT_BREATHING, Model: 0x01, ColorKey: color.Name, Color: []byte{color.R, color.G, color.B}}
UpdateMenu(menu, app, statusBarItem)
})
stateVal := DeviceStateCache[key]
if dev.CurrentEffect == KBD_EFFECT_BREATHING && stateVal != nil && stateVal.GetModel() == 0x01 && stateVal.GetColorKey() == color.Name {
item.SetState(appkit.ControlStateValueOn)
}
singleSub.AddItem(item)
}
stateVal = DeviceStateCache[key]
if dev.CurrentEffect == KBD_EFFECT_BREATHING && stateVal != nil && stateVal.GetModel() == 0x01 {
singleItem.SetState(appkit.ControlStateValueOn)
}
singleItem.SetSubmenu(singleSub)
breathRootMenu.AddItem(singleItem)
// B2. 双色呼吸 (红 <-> 自选)
dualItem := appkit.NewMenuItemWithAction("双色模式", "", func(sender objc.Object) {})
dualSub := appkit.NewMenuWithTitle("Dual")
for _, multiColor := range PRESET_MULTI_COLORS {
item := appkit.NewMenuItemWithAction(multiColor.Name, "", func(sender objc.Object) {
if stateVal != nil {
dev.SetBreathingColor(0x02, multiColor.Color1, multiColor.Color2, byte(stateVal.GetSpeed()))
} else {
dev.SetBreathingColor(0x02, multiColor.Color1, multiColor.Color2, byte(1))
}
DeviceStateCache[key] = &BreathingState{Effect: KBD_EFFECT_BREATHING, Model: 0x02, MultiColorKey: multiColor.Name, Color1: multiColor.Color1, Color2: multiColor.Color2}
UpdateMenu(menu, app, statusBarItem)
})
if dev.CurrentEffect == KBD_EFFECT_BREATHING && stateVal != nil && stateVal.GetModel() == 0x02 && stateVal.GetMultiColorKey() == multiColor.Name {
item.SetState(appkit.ControlStateValueOn)
}
dualSub.AddItem(item)
}
stateVal = DeviceStateCache[key]
if dev.CurrentEffect == KBD_EFFECT_BREATHING && stateVal != nil && stateVal.GetModel() == 0x02 {
dualItem.SetState(appkit.ControlStateValueOn)
}
dualItem.SetSubmenu(dualSub)
breathRootMenu.AddItem(dualItem)
// B3. 随机
randItem := appkit.NewMenuItemWithAction("随机颜色", "", func(sender objc.Object) {
DeviceStateCache[key] = &BreathingState{Effect: KBD_EFFECT_BREATHING, Model: 0x03}
UpdateMenu(menu, app, statusBarItem)
})
stateVal = DeviceStateCache[key]
if dev.CurrentEffect == KBD_EFFECT_BREATHING && stateVal != nil && stateVal.GetModel() == 0x03 {
randItem.SetState(appkit.ControlStateValueOn)
}
breathRootMenu.AddItem(randItem)
breathRootMenu.AddItem(appkit.MenuItem_SeparatorItem())
breathItem.SetSubmenu(breathRootMenu)
menu.AddItem(breathItem)
// --- C. 波浪模式 ---
waveItem := appkit.NewMenuItemWithAction("🌊 波浪模式", "", func(sender objc.Object) {
//dev.ApplyEffect(KBD_EFFECT_WAVE, 0x01, 0x00, 0x00)
key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
deviceStateCache[key] = State{Effect: KBD_EFFECT_WAVE, Color: ""}
UpdateMenu(menu, app, statusBarItem)
})
if dev.CurrentEffect == KBD_EFFECT_WAVE {
waveItem.SetState(appkit.ControlStateValueOn)
}
menu.AddItem(waveItem)
//// --- C. 波浪模式 ---
//waveItem := appkit.NewMenuItemWithAction("🌊 波浪模式", "", func(sender objc.Object) {
// //dev.ApplyEffect(KBD_EFFECT_WAVE, 0x01, 0x00, 0x00)
// key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
// deviceStateCache[key] = State{Effect: KBD_EFFECT_WAVE, Color: ""}
// UpdateMenu(menu, app, statusBarItem)
//})
//if dev.CurrentEffect == KBD_EFFECT_WAVE {
// waveItem.SetState(appkit.ControlStateValueOn)
//}
//menu.AddItem(waveItem)
// --- D. 响应模式 ---
reactiveItem := appkit.NewMenuItemWithAction("🌊 响应模式", "", func(sender objc.Object) {
//dev.ApplyEffect(KBD_EFFECT_WAVE, 0x01, 0x00, 0x00)
key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
deviceStateCache[key] = State{Effect: KBD_EFFECT_REACTIVE, Color: ""}
UpdateMenu(menu, app, statusBarItem)
})
if dev.CurrentEffect == KBD_EFFECT_REACTIVE {
reactiveItem.SetState(appkit.ControlStateValueOn)
}
menu.AddItem(reactiveItem)
//reactiveItem := appkit.NewMenuItemWithAction("🌊 响应模式", "", func(sender objc.Object) {
// //dev.ApplyEffect(KBD_EFFECT_WAVE, 0x01, 0x00, 0x00)
// key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
// deviceStateCache[key] = State{Effect: KBD_EFFECT_REACTIVE, Color: ""}
// UpdateMenu(menu, app, statusBarItem)
//})
//if dev.CurrentEffect == KBD_EFFECT_REACTIVE {
// reactiveItem.SetState(appkit.ControlStateValueOn)
//}
//menu.AddItem(reactiveItem)
// --- E. 关灯模式 --- 关灯
offLightsItem := appkit.NewMenuItemWithAction("关灯模式", "", func(sender objc.Object) {
dev.SetOffLightsColor([]byte{0x00, 0x00, 0x00})
key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
deviceStateCache[key] = State{Effect: KBD_EFFECT_OFFLIGHTS, Color: ""}
UpdateMenu(menu, app, statusBarItem)
})
if dev.CurrentEffect == KBD_EFFECT_OFFLIGHTS {
offLightsItem.SetState(appkit.ControlStateValueOn)
}
menu.AddItem(offLightsItem)
//offLightsItem := appkit.NewMenuItemWithAction("关灯模式", "", func(sender objc.Object) {
// dev.SetOffLightsColor([]byte{0x00, 0x00, 0x00})
// //key := fmt.Sprintf("%s|%s|%d", dev.Serial, dev.Product, dev.ProductID)
// deviceStateCache[key] = State{Effect: KBD_EFFECT_OFFLIGHTS, Color: ""}
// UpdateMenu(menu, app, statusBarItem)
//})
//if dev.CurrentEffect == KBD_EFFECT_OFFLIGHTS {
// offLightsItem.SetState(appkit.ControlStateValueOn)
//}
//menu.AddItem(offLightsItem)
//modeMenuItem.SetSubmenu(modeSubMenu)
//menu.AddItem(modeMenuItem)

View File

@@ -168,9 +168,9 @@ func ScanDevices() map[string]*RazerDevice {
}
// 从缓存恢复状态
if s, found := deviceStateCache[key]; found {
grouped[key].CurrentEffect = s.Effect
grouped[key].CurrentColor = s.Color
if s, found := DeviceStateCache[key]; found {
grouped[key].CurrentEffect = s.GetEffect()
//grouped[key].CurrentColor = s.Color
}
}