Files
silver_pose/v2/internal/ui/window.go
T

222 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ui
import (
"fmt"
"strings"
"github.com/lxn/walk"
d "github.com/lxn/walk/declarative"
"github.com/lxn/win"
"silverpose/v2/internal/alert"
"silverpose/v2/internal/fall"
"silverpose/v2/internal/monitor"
)
type Settings struct {
SourceEnvironment string
ModelSHA256 string
RuntimeSummary string
EventSummary string
}
type Commands struct {
Start func()
Stop func()
}
// Window is an observer of monitor updates. It does not own a stream, runtime,
// fall engine, or event artifact writer.
type Window struct {
main *walk.MainWindow
imageView *walk.ImageView
connectionLabel *walk.TextLabel
stateLabel *walk.TextLabel
eventLabel *walk.TextLabel
startButton *walk.PushButton
stopButton *walk.PushButton
model ViewModel
bitmap *walk.Bitmap
seenAlerts map[string]struct{}
commands Commands
}
func NewWindow(settings Settings, commands Commands) (*Window, error) {
spec := MonitorWindowSpec()
window := &Window{model: NewViewModel(), seenAlerts: make(map[string]struct{}), commands: commands}
window.model.SetSourceReady(settings.SourceEnvironment, settings.SourceEnvironment != "")
background := d.SolidColorBrush{Color: walk.RGB(234, 241, 248)}
surface := d.SolidColorBrush{Color: walk.RGB(255, 255, 255)}
if err := (d.MainWindow{
AssignTo: &window.main,
Title: spec.Title,
Size: d.Size{Width: spec.Width, Height: spec.Height},
MinSize: d.Size{Width: 960, Height: 640},
Background: background,
Layout: d.VBox{},
Children: []d.Widget{
d.TabWidget{
Pages: []d.TabPage{
{
Title: "监控",
Layout: d.HBox{},
Children: []d.Widget{
d.Composite{
Background: surface,
Layout: d.VBox{},
StretchFactor: 3,
Children: []d.Widget{
d.TextLabel{Text: "实时画面", ToolTipText: "显示完成推理与姿态叠加后的最新画面"},
d.ImageView{AssignTo: &window.imageView, Mode: d.ImageViewModeZoom, StretchFactor: 8, ToolTipText: "实时姿态画面"},
},
},
d.Composite{
Background: surface,
Layout: d.VBox{},
StretchFactor: 2,
Children: []d.Widget{
d.TextLabel{Text: "运行状态"},
d.TextLabel{AssignTo: &window.connectionLabel, Text: window.model.ConnectionText},
d.TextLabel{AssignTo: &window.stateLabel, Text: window.model.StateText},
d.TextLabel{AssignTo: &window.eventLabel, Text: window.model.EventText},
d.PushButton{AssignTo: &window.startButton, Text: "开始监控", ToolTipText: "使用当前配置启动单路监控", OnClicked: window.start},
d.PushButton{AssignTo: &window.stopButton, Text: "停止监控", Enabled: false, ToolTipText: "停止监控并释放本地运行资源", OnClicked: window.stop},
d.TextLabel{Text: "红色仅表示已确认摔倒。连接异常不会触发报警。"},
},
},
},
},
{
Title: "设置",
Layout: d.VBox{},
Children: []d.Widget{
d.TextLabel{Text: "来源与运行依赖(只读,不显示 RTSP 地址或密码)"},
d.TextLabel{Text: window.model.SettingsSummary},
d.TextLabel{Text: "模型 SHA-256:" + shortHash(settings.ModelSHA256)},
d.TextLabel{Text: "运行依赖:" + settings.RuntimeSummary},
d.TextLabel{Text: "事件参数:" + settings.EventSummary},
d.TextLabel{Text: "参数在配置文件中维护;修改后请停止并重新开始监控。"},
},
},
},
},
},
}).Create(); err != nil {
return nil, err
}
window.main.Closing().Attach(func(canceled *bool, reason walk.CloseReason) {
window.stop()
if window.bitmap != nil {
window.bitmap.Dispose()
window.bitmap = nil
}
})
return window, nil
}
func (window *Window) Run() int { return window.main.Run() }
func (window *Window) start() {
if window.commands.Start != nil {
window.commands.Start()
}
window.startButton.SetEnabled(false)
window.stopButton.SetEnabled(true)
}
func (window *Window) stop() {
if window.commands.Stop != nil {
window.commands.Stop()
}
if window.startButton != nil {
window.startButton.SetEnabled(true)
}
if window.stopButton != nil {
window.stopButton.SetEnabled(false)
}
}
// Present runs only the final state assignment on the UI thread.
func (window *Window) Present(update monitor.Update) {
window.main.Synchronize(func() {
state := highestState(update.Result)
window.model.Apply(state, update.SourceStatus)
if update.SourceMessage != "" {
window.model.ConnectionText = "视频流:" + update.SourceMessage
}
_ = window.connectionLabel.SetText(window.model.ConnectionText)
_ = window.stateLabel.SetText(window.model.StateText)
_ = window.eventLabel.SetText(window.model.EventText)
if update.Image == nil {
return
}
bitmap, err := walk.NewBitmapFromImage(update.Image)
if err != nil {
return
}
if err := window.imageView.SetImage(bitmap); err != nil {
bitmap.Dispose()
return
}
previous := window.bitmap
window.bitmap = bitmap
if previous != nil {
previous.Dispose()
}
})
}
// PresentAlert is called by a consumer of Monitor.Alerts. Event IDs are checked
// again at the UI boundary so a reconnect/repaint cannot replay sound or popup.
func (window *Window) PresentAlert(record alert.Record) {
if !record.Written {
return
}
window.main.Synchronize(func() {
if _, seen := window.seenAlerts[record.Event.EventID]; seen {
return
}
window.seenAlerts[record.Event.EventID] = struct{}{}
win.MessageBeep(win.MB_ICONHAND)
message := fmt.Sprintf("确认摔倒\n人员:%s\n延迟:%.2f 秒\n截图:%s", record.Event.TrackID, record.Event.LatencySeconds, record.ScreenshotPath)
walk.MsgBox(window.main, "Silver Pose V2 · 确认摔倒", message, walk.MsgBoxOK|walk.MsgBoxIconError)
})
}
// ReportIssue communicates a recoverable startup/worker issue without using
// critical-red fall language or exposing configuration values.
func (window *Window) ReportIssue(message string) {
window.main.Synchronize(func() {
window.model.ConnectionText = "视频流:" + message
window.model.Critical = false
_ = window.connectionLabel.SetText(window.model.ConnectionText)
_ = window.stateLabel.SetText("检测状态:正常")
})
}
func highestState(result fall.FrameResult) fall.State {
state := fall.Normal
for _, person := range result.People {
switch person.State {
case fall.Confirmed:
return fall.Confirmed
case fall.Suspect:
if state == fall.Normal || state == fall.Recovering {
state = fall.Suspect
}
case fall.Recovering:
if state == fall.Normal {
state = fall.Recovering
}
}
}
return state
}
func shortHash(value string) string {
if len(value) <= 12 {
return value
}
return strings.ToLower(value[:12]) + "…"
}