8 Commits

Author SHA1 Message Date
messypy
70bafabee9 Persist current world history
All checks were successful
build-windows-exe / build (push) Successful in 1m50s
2026-07-28 00:51:15 +09:00
messypy
8e704980c1 Fix history current visit labels
All checks were successful
build-windows-exe / build (push) Successful in 1m5s
2026-07-28 00:47:37 +09:00
messypy
1b3d978137 Allow resizing native GUI window
All checks were successful
build-windows-exe / build (push) Successful in 1m24s
2026-07-28 00:44:28 +09:00
messypy
51c00f2a75 Fallback history export to VRChat logs
All checks were successful
build-windows-exe / build (push) Successful in 1m40s
2026-07-28 00:37:56 +09:00
messypy
6d318af329 Give settings export controls more space
All checks were successful
build-windows-exe / build (push) Successful in 1m46s
2026-07-28 00:31:42 +09:00
messypy
a017170c71 Align settings controls
All checks were successful
build-windows-exe / build (push) Successful in 2m9s
2026-07-28 00:20:57 +09:00
messypy
d38d2edee3 Remove duplicate font size buttons
All checks were successful
build-windows-exe / build (push) Successful in 2m5s
2026-07-28 00:14:04 +09:00
messypy
01d5799914 Fix settings panel control overlap
All checks were successful
build-windows-exe / build (push) Successful in 2m28s
2026-07-28 00:05:41 +09:00
3 changed files with 307 additions and 71 deletions

View File

@@ -228,12 +228,14 @@ func historyVisitSource() []historyVisitRecord {
})
}
if label, since, ok := currentWorldVisitInfo(); ok && strings.TrimSpace(label) != "" && !since.IsZero() {
records = append(records, historyVisitRecord{
Key: historyVisitKey(label, since, time.Time{}, "", "", true),
WorldLabel: label,
Start: since,
Current: true,
})
if !hasOpenHistoryVisit(records, label, since) {
records = append(records, historyVisitRecord{
Key: historyVisitKey(label, since, time.Time{}, "", "", true),
WorldLabel: label,
Start: since,
Current: true,
})
}
}
sort.SliceStable(records, func(i, j int) bool {
ti := records[i].End
@@ -252,6 +254,22 @@ func historyVisitSource() []historyVisitRecord {
return records
}
func hasOpenHistoryVisit(records []historyVisitRecord, label string, since time.Time) bool {
label = strings.TrimSpace(label)
for _, record := range records {
if !record.End.IsZero() || record.Start.IsZero() {
continue
}
if !record.Start.Equal(since) {
continue
}
if strings.EqualFold(strings.TrimSpace(record.WorldLabel), label) {
return true
}
}
return false
}
func historyVisitKey(label string, start, end time.Time, worldID, instanceID string, current bool) string {
key := strings.TrimSpace(label)
if worldID != "" {
@@ -342,6 +360,45 @@ func historyEventsFromSnapshot() []historyEventRecord {
return out
}
func historyEventsWithFallback() []historyEventRecord {
events := historyEventsFromSnapshot()
if len(events) > 0 {
return events
}
users, ok := currentUsersFromVRChatLog()
if !ok || len(users) == 0 {
return nil
}
out := make([]historyEventRecord, 0, len(users))
for _, user := range users {
name := strings.TrimSpace(user.Name)
if name == "" {
continue
}
if !user.LastJoin.IsZero() {
out = append(out, historyEventRecord{
At: user.LastJoin,
Kind: "join",
Name: name,
})
}
if !user.Present && !user.LastLeave.IsZero() {
out = append(out, historyEventRecord{
At: user.LastLeave,
Kind: "leave",
Name: name,
})
}
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].At.Equal(out[j].At) {
return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name)
}
return out[i].At.Before(out[j].At)
})
return out
}
func decodeHistoryEventsSnapshot(b []byte) []historyEventRecord {
type snapshot struct {
UpdatedAt time.Time `json:"updated_at"`
@@ -408,7 +465,7 @@ func historyVisitsForDay(app *guiApp, day time.Time) []historyDetailRow {
if !historyInQueryRange(day, q) {
return nil
}
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
return historyVisitsForDayFromData(app, day, visits, events, q)
}
@@ -502,7 +559,7 @@ func historyCalendarCellsForMonth(app *guiApp) []historyCalendarCell {
monthStart = time.Now()
}
monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location())
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
return historyCalendarCellsForMonthFromData(app, monthStart, visits, events, q)
}
@@ -591,7 +648,7 @@ func exportHistoryDay(app *guiApp, day time.Time, selected map[string]bool) (str
}
func exportHistoryDayFromRequest(req historyExportRequest) (string, error) {
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
rows := append([]historyDetailRow(nil), req.Rows...)
if len(rows) == 0 {
@@ -795,7 +852,7 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
if b == nil {
return
}
guests := readGuestSnapshot()
guests := guestSnapshotForExport()
dayStart, dayEnd := historyDayBounds(day)
type guestLine struct {
name string
@@ -848,6 +905,24 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
}
}
func guestSnapshotForExport() []userState {
guests := readGuestSnapshot()
if len(guests) > 0 {
out := make([]userState, 0, len(guests))
for _, guest := range guests {
out = append(out, userState{
Name: guest.Name,
Present: guest.Present,
LastJoin: guest.LastJoin,
LastLeave: guest.LastLeave,
})
}
return out
}
users, _ := currentUsersFromJoinLeave()
return users
}
func cloneHistorySelection(selected map[string]bool) map[string]bool {
if len(selected) == 0 {
return nil
@@ -887,7 +962,7 @@ func refreshHistoryCache(app *guiApp, force bool) bool {
if !force && app.historyCacheLoaded && !app.historyReloadPending {
return false
}
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
q := historyQueryFromApp(app)
app.historyRows = historyRows()
@@ -1218,9 +1293,17 @@ func paintHistoryDetailPane(hwnd uintptr, app *guiApp) uintptr {
border = clrAccentGreen
}
drawSettingsBox(hdc, row.Rect, fill, border)
duration := humanDurationLabel(row.Start, row.End, row.Current)
durationWidth := measureTextWidth(hdc, duration, app.hFont)
durationX := row.Rect.Right - 12 - durationWidth
if durationX < row.Rect.Left+260 {
durationX = row.Rect.Left + 260
}
drawPaneText(hdc, row.Rect.Left+12, y+6, "入室 "+historyEntryTimeLabel(row.Start, false, row.Current), app.hFont, clrMutedText)
drawPaneText(hdc, row.Rect.Left+146, y+6, "退室 "+historyEntryTimeLabel(row.End, true, row.Current), app.hFont, clrMutedText)
drawPaneText(hdc, row.Rect.Right-110, y+6, humanDurationLabel(row.Start, row.End, row.Current), app.hFont, clrMutedText)
drawPaneText(hdc, row.Rect.Left+136, y+6, "退室 "+historyEntryTimeLabel(row.End, true, row.Current), app.hFont, clrMutedText)
if durationX+durationWidth < row.Rect.Right-8 {
drawPaneText(hdc, durationX, y+6, duration, app.hFont, clrMutedText)
}
label := ellipsizeTextToWidth(hdc, row.WorldLabel, row.Rect.Right-row.Rect.Left-150, app.joinBoldHFont)
if label == "" {
label = "(unknown)"
@@ -1316,12 +1399,12 @@ func isMidnight(t time.Time) bool {
func historyEntryTimeLabel(t time.Time, isEnd bool, current bool) string {
if t.IsZero() {
if current {
if current && isEnd {
return "now"
}
return ""
}
if current {
if current && isEnd {
return "now"
}
if isEnd && isMidnight(t) {

View File

@@ -28,6 +28,7 @@ const (
wsVisible = 0x10000000
wsChild = 0x40000000
wsPopup = 0x80000000
wsSizeBox = 0x00040000
wsClipChildren = 0x02000000
wsClipSiblings = 0x04000000
wsBorder = 0x00800000
@@ -42,6 +43,9 @@ const (
wmPaint = 0x000F
wmEraseBkgnd = 0x0014
wmSize = 0x0005
wmNCCalcSize = 0x0083
wmNCPaint = 0x0085
wmGetMinMaxInfo = 0x0024
wmVScroll = 0x0115
wmMouseWheel = 0x020A
wmCloseGUI = 0x0010
@@ -58,6 +62,14 @@ const (
enKillFocus = 0x0200
bnClicked = 0x0000
htCaption = 2
htLeft = 10
htRight = 11
htTop = 12
htTopLeft = 13
htTopRight = 14
htBottom = 15
htBottomLeft = 16
htBottomRight = 17
tbButtonStructSize = 0x041E
tbAddButtons = 0x0414
tbAddStringW = 0x044D
@@ -98,11 +110,14 @@ const (
contentTop = headerHeight + navBarHeight
bottomBarHeight = 48
windowWidth = 760
minWindowWidth = 760
minWindowHeight = contentTop + leftPaneHeight + bottomBarHeight
resizeGripSize = 8
leftPaneWidth = 445
leftPaneHeight = 560
rightPaneWidth = windowWidth - leftPaneWidth
settingsPaneWidth = rightPaneWidth
settingsPaneHeight = leftPaneHeight
settingsPaneHeight = 700
clrMainBg = 0x00311c0b
clrHeaderBg = 0x004d2c08
clrNavBg = 0x0024150b
@@ -228,8 +243,6 @@ type guiApp struct {
tabSettings uintptr
btnTopMost uintptr
btnAutoUnmute uintptr
btnFontDown uintptr
btnFontUp uintptr
activeTab string
topMost bool
autoUnmuteOnLeave bool
@@ -578,6 +591,19 @@ func runNativeGUI(initialTab string) error {
return paintMainWindow(hwnd, &app)
case wmEraseBkgnd:
return 1
case wmNCCalcSize:
return 0
case wmNCPaint:
return 0
case wmGetMinMaxInfo:
applyMinMaxInfo(lParam)
return 0
case wmSize:
layoutMainWindow(&app)
if invalidateRectProc != nil {
invalidateRectProc.Call(hwnd, 0, 1)
}
return 0
case wmCreateGUI:
app.hwnd = hwnd
buttonClass, _ := syscall.UTF16PtrFromString("BUTTON")
@@ -586,8 +612,6 @@ func runNativeGUI(initialTab string) error {
rightTitle, _ := syscall.UTF16PtrFromString("")
topMostTitle, _ := syscall.UTF16PtrFromString("Top most")
autoUnmuteTitle, _ := syscall.UTF16PtrFromString("Leave unmute")
fontDownTitle, _ := syscall.UTF16PtrFromString("A-")
fontUpTitle, _ := syscall.UTF16PtrFromString("A+")
browseTitle, _ := syscall.UTF16PtrFromString("参照")
saveTitle, _ := syscall.UTF16PtrFromString("保存")
toolbarTitle, _ := syscall.UTF16PtrFromString("")
@@ -635,41 +659,43 @@ func runNativeGUI(initialTab string) error {
app.settingsPaneHwnd, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(paneClassName)), uintptr(unsafe.Pointer(settingsPaneTitle)), paneStyle, 0, contentTop, windowWidth, settingsPaneHeight, hwnd, idLog+1, hInstance, 0)
app.btnTopMost, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(topMostTitle)), buttonStyle, leftPaneWidth+32, contentTop+86, 140, 30, hwnd, 3005, hInstance, 0)
app.btnAutoUnmute, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(autoUnmuteTitle)), buttonStyle, leftPaneWidth+32, contentTop+132, 180, 30, hwnd, 3008, hInstance, 0)
app.btnFontDown, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(fontDownTitle)), buttonStyle, leftPaneWidth+32, contentTop+178, 52, 30, hwnd, 3006, hInstance, 0)
app.btnFontUp, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(fontUpTitle)), buttonStyle, leftPaneWidth+92, contentTop+178, 52, 30, hwnd, 3007, hInstance, 0)
exportDirTitle, _ := syscall.UTF16PtrFromString("")
exportDirRect := settingsHistoryExportDirRect()
app.settingsExportDirEdit, _, _ = createWindowEx.Call(
0,
uintptr(unsafe.Pointer(editClassTitle)),
uintptr(unsafe.Pointer(exportDirTitle)),
uintptr(wsChild|wsVisible|wsBorder|wsTabstop|esAutohscroll),
116, 410, 520, 24,
uintptr(exportDirRect.Left), uintptr(exportDirRect.Top), uintptr(exportDirRect.Right-exportDirRect.Left), uintptr(exportDirRect.Bottom-exportDirRect.Top),
app.settingsPaneHwnd, idHistoryExportDir, hInstance, 0,
)
browseStyle := uintptr(wsChild | wsTabstop)
browseRect := settingsHistoryExportBrowseRect()
app.settingsExportBrowseBtn, _, _ = createWindowEx.Call(
0,
uintptr(unsafe.Pointer(buttonClass)),
uintptr(unsafe.Pointer(browseTitle)),
browseStyle,
644, 410, 92, 24,
uintptr(browseRect.Left), uintptr(browseRect.Top), uintptr(browseRect.Right-browseRect.Left), uintptr(browseRect.Bottom-browseRect.Top),
app.settingsPaneHwnd, idHistoryExportBrowse, hInstance, 0,
)
exportCustomTitle, _ := syscall.UTF16PtrFromString("")
customRect := settingsHistoryExportCustomRect()
app.settingsExportCustomEdit, _, _ = createWindowEx.Call(
0,
uintptr(unsafe.Pointer(editClassTitle)),
uintptr(unsafe.Pointer(exportCustomTitle)),
uintptr(wsChild|wsVisible|wsBorder|wsTabstop|esAutohscroll),
116, 520, 520, 24,
uintptr(customRect.Left), uintptr(customRect.Top), uintptr(customRect.Right-customRect.Left), uintptr(customRect.Bottom-customRect.Top),
app.settingsPaneHwnd, idHistoryExportCustom, hInstance, 0,
)
saveRect := settingsHistoryExportCustomSaveRect()
app.settingsExportCustomSaveBtn, _, _ = createWindowEx.Call(
0,
uintptr(unsafe.Pointer(buttonClass)),
uintptr(unsafe.Pointer(saveTitle)),
browseStyle,
644, 520, 92, 24,
uintptr(saveRect.Left), uintptr(saveRect.Top), uintptr(saveRect.Right-saveRect.Left), uintptr(saveRect.Bottom-saveRect.Top),
app.settingsPaneHwnd, idHistoryExportSave, hInstance, 0,
)
applyRefreshTimer(hwnd, &app)
@@ -798,8 +824,16 @@ func runNativeGUI(initialTab string) error {
case wmLButtonDown:
x := int32(lParam & 0xffff)
y := int32((lParam >> 16) & 0xffff)
var rc winRect
if getClientRectProc != nil {
getClientRectProc.Call(hwnd, uintptr(unsafe.Pointer(&rc)))
}
if ht := resizeHitTest(x, y, rc); ht != 0 {
releaseCapture.Call()
sendMessageProc.Call(hwnd, wmNCLButtonDown, uintptr(ht), 0)
return 0
}
if y >= 0 && y < headerHeight {
var rc winRect
width := int32(windowWidth)
if getClientRectProc != nil {
getClientRectProc.Call(hwnd, uintptr(unsafe.Pointer(&rc)))
@@ -952,7 +986,7 @@ func runNativeGUI(initialTab string) error {
}
guiLog("stage=create window")
app.hwnd, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(title)), wsPopup|wsVisible|wsClipChildren, 200, 120, windowWidth, contentTop+leftPaneHeight+bottomBarHeight, 0, 0, hInstance, 0)
app.hwnd, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(title)), wsPopup|wsSizeBox|wsVisible|wsClipChildren, 200, 120, windowWidth, contentTop+leftPaneHeight+bottomBarHeight, 0, 0, hInstance, 0)
if app.hwnd == 0 {
guiLog("stage=create window failed")
return fmt.Errorf("create window failed")
@@ -1212,7 +1246,7 @@ func refreshGUI(setWindowText *syscall.LazyProc, app *guiApp, reloadData bool) {
if app.settingsPaneHwnd != 0 {
showWindowProc.Call(app.settingsPaneHwnd, swHideControl)
}
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.btnFontDown, app.btnFontUp} {
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute} {
if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl)
}
@@ -1253,7 +1287,7 @@ func refreshSettingsControls(showWindowProc, setWindowPosProc *syscall.LazyProc,
return
}
app.lastSettingsVisible = true
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.btnFontDown, app.btnFontUp} {
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute} {
if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl)
}
@@ -1277,7 +1311,7 @@ func refreshSettingsControls(showWindowProc, setWindowPosProc *syscall.LazyProc,
if app.settingsPaneHwnd != 0 {
showWindowProc.Call(app.settingsPaneHwnd, swHideControl)
}
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.btnFontDown, app.btnFontUp, app.settingsExportDirEdit, app.settingsExportCustomEdit, app.settingsExportBrowseBtn, app.settingsExportCustomSaveBtn} {
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.settingsExportDirEdit, app.settingsExportCustomEdit, app.settingsExportBrowseBtn, app.settingsExportCustomSaveBtn} {
if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl)
}
@@ -1613,33 +1647,43 @@ func resizeGUIForJoinContent(app *guiApp, contentHeight int) {
if contentHeight < leftPaneHeight {
contentHeight = leftPaneHeight
}
if w, h, ok := clientSize(app.hwnd); ok {
if h > contentTop+contentHeight+bottomBarHeight {
contentHeight = h - contentTop - bottomBarHeight
}
if w < minWindowWidth {
w = minWindowWidth
}
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + contentHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, uintptr(w), uintptr(windowHeight), resizeFlags)
layoutMainWindow(app)
return
}
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + contentHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags)
for _, item := range []struct {
hwnd uintptr
width int
}{
{app.leftHwnd, leftPaneWidth},
{app.rightHwnd, rightPaneWidth},
{app.settingsPaneHwnd, settingsPaneWidth},
} {
if item.hwnd != 0 {
setWindowPosProc.Call(item.hwnd, 0, 0, 0, uintptr(item.width), uintptr(contentHeight), resizeFlags)
}
}
layoutMainWindow(app)
}
func resizeGUIForSettings(app *guiApp) {
if app == nil || setWindowPosProc == nil || app.hwnd == 0 {
return
}
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + leftPaneHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags)
if app.settingsPaneHwnd != 0 {
setWindowPosProc.Call(app.settingsPaneHwnd, 0, 0, uintptr(contentTop), windowWidth, uintptr(leftPaneHeight), resizeFlags)
contentHeight := settingsPaneHeight
width := windowWidth
if w, h, ok := clientSize(app.hwnd); ok {
if w > width {
width = w
}
if h > contentTop+contentHeight+bottomBarHeight {
contentHeight = h - contentTop - bottomBarHeight
}
}
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + contentHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, uintptr(width), uintptr(windowHeight), resizeFlags)
layoutMainWindow(app)
}
type winRect struct {
@@ -1649,6 +1693,110 @@ type winRect struct {
Bottom int32
}
type winPoint struct {
X int32
Y int32
}
type minMaxInfo struct {
Reserved winPoint
MaxSize winPoint
MaxPosition winPoint
MinTrackSize winPoint
MaxTrackSize winPoint
}
func applyMinMaxInfo(lParam uintptr) {
if lParam == 0 {
return
}
info := (*minMaxInfo)(unsafe.Pointer(lParam))
info.MinTrackSize.X = minWindowWidth
info.MinTrackSize.Y = minWindowHeight
}
func clientSize(hwnd uintptr) (int, int, bool) {
if hwnd == 0 || getClientRectProc == nil {
return 0, 0, false
}
var rc winRect
getClientRectProc.Call(hwnd, uintptr(unsafe.Pointer(&rc)))
w := int(rc.Right - rc.Left)
h := int(rc.Bottom - rc.Top)
if w <= 0 || h <= 0 {
return 0, 0, false
}
return w, h, true
}
func resizeHitTest(x, y int32, rc winRect) int32 {
if rc.Right <= 0 || rc.Bottom <= 0 {
return 0
}
onLeft := x >= 0 && x < resizeGripSize
onRight := x <= rc.Right && x > rc.Right-resizeGripSize
onTop := y >= 0 && y < resizeGripSize
onBottom := y <= rc.Bottom && y > rc.Bottom-resizeGripSize
switch {
case onTop && onLeft:
return htTopLeft
case onTop && onRight:
return htTopRight
case onBottom && onLeft:
return htBottomLeft
case onBottom && onRight:
return htBottomRight
case onLeft:
return htLeft
case onRight:
return htRight
case onTop:
return htTop
case onBottom:
return htBottom
default:
return 0
}
}
func layoutMainWindow(app *guiApp) {
if app == nil || app.hwnd == 0 || setWindowPosProc == nil {
return
}
clientW, clientH, ok := clientSize(app.hwnd)
if !ok {
return
}
contentH := clientH - contentTop - bottomBarHeight
if contentH < 1 {
contentH = 1
}
leftW := leftPaneWidth
if clientW < leftW+160 {
leftW = clientW / 2
if leftW < 240 {
leftW = 240
}
}
rightW := clientW - leftW
if rightW < 1 {
rightW = 1
}
resizeFlags := hwndNoZOrder | hwndNoActivate
if app.navBarHwnd != 0 {
setWindowPosProc.Call(app.navBarHwnd, 0, 0, uintptr(headerHeight), uintptr(clientW), uintptr(navBarHeight), resizeFlags)
}
if app.leftHwnd != 0 {
setWindowPosProc.Call(app.leftHwnd, 0, 0, uintptr(contentTop), uintptr(leftW), uintptr(contentH), resizeFlags)
}
if app.rightHwnd != 0 {
setWindowPosProc.Call(app.rightHwnd, 0, uintptr(leftW), uintptr(contentTop), uintptr(rightW), uintptr(contentH), resizeFlags)
}
if app.settingsPaneHwnd != 0 {
setWindowPosProc.Call(app.settingsPaneHwnd, 0, 0, uintptr(contentTop), uintptr(clientW), uintptr(contentH), resizeFlags)
}
}
type paintStruct struct {
Hdc uintptr
FErase int32
@@ -1869,22 +2017,17 @@ func paintSettingsPane(hwnd uintptr, app *guiApp) uintptr {
drawPaneText(hdc, 24, 68, "表示", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsTopMostRect(), "Top most", onOffBool(app.topMost), app.hFont, app.topMost)
drawSettingsWideButton(hdc, settingsAutoUnmuteRect(), "Leave unmute", onOffBool(app.autoUnmuteOnLeave), app.hFont, app.autoUnmuteOnLeave)
drawPaneText(hdc, 24, 190, "文字サイズ", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 190, "更新間隔", app.hFont, clrMutedText)
drawSettingsRefreshControl(hdc, app)
drawPaneText(hdc, 24, 314, "文字サイズ", app.hFont, clrMutedText)
drawSettingsFontControl(hdc, app)
drawPaneText(hdc, 24, 390, "履歴 Export", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 414, "出力先フォルダ", app.hFont, clrMutedText)
drawSettingsBox(hdc, settingsHistoryExportDirRect(), 0x0038281a, clrPaneBorder)
drawPaneText(hdc, 120, 414, exportDirDisplayText(app.historyExportDir), app.hFont, clrText)
drawSettingsWideButton(hdc, settingsHistoryExportBrowseRect(), "参照", "", app.hFont, true)
drawPaneText(hdc, 24, 400, "履歴 Export", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 430, "出力先フォルダ", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsHistoryExportTimeRect(), "Time", onOffBool(app.historyExportIncludeTime), app.hFont, app.historyExportIncludeTime)
drawSettingsWideButton(hdc, settingsHistoryExportWorldRect(), "World", onOffBool(app.historyExportIncludeWorld), app.hFont, app.historyExportIncludeWorld)
drawSettingsWideButton(hdc, settingsHistoryExportJoinLeaveRect(), "Join/Leave", onOffBool(app.historyExportIncludeJoinLeave), app.hFont, app.historyExportIncludeJoinLeave)
drawSettingsWideButton(hdc, settingsHistoryExportCustomToggleRect(), "Custom", onOffBool(app.historyExportCustomEnabled), app.hFont, app.historyExportCustomEnabled)
drawPaneText(hdc, 24, 522, "Custom 条件", app.hFont, clrMutedText)
drawSettingsBox(hdc, settingsHistoryExportCustomRect(), 0x0038281a, clrPaneBorder)
drawPaneText(hdc, 120, 522, previewLine(app.historyExportCustom, 60), app.hFont, clrText)
drawSettingsWideButton(hdc, settingsHistoryExportCustomSaveRect(), "保存", "", app.hFont, true)
drawPaneText(hdc, 24, 618, "Custom 条件", app.hFont, clrMutedText)
return 0
}
@@ -1925,35 +2068,35 @@ func settingsFontUpRect() winRect {
}
func settingsHistoryExportDirRect() winRect {
return winRect{Left: 116, Top: 410, Right: 636, Bottom: 434}
return winRect{Left: 24, Top: 458, Right: 636, Bottom: 490}
}
func settingsHistoryExportBrowseRect() winRect {
return winRect{Left: 644, Top: 410, Right: 736, Bottom: 434}
return winRect{Left: 644, Top: 458, Right: 736, Bottom: 490}
}
func settingsHistoryExportTimeRect() winRect {
return winRect{Left: 24, Top: 442, Right: 360, Bottom: 476}
return winRect{Left: 24, Top: 510, Right: 360, Bottom: 546}
}
func settingsHistoryExportWorldRect() winRect {
return winRect{Left: 376, Top: 442, Right: 736, Bottom: 476}
return winRect{Left: 376, Top: 510, Right: 736, Bottom: 546}
}
func settingsHistoryExportJoinLeaveRect() winRect {
return winRect{Left: 24, Top: 480, Right: 360, Bottom: 514}
return winRect{Left: 24, Top: 558, Right: 360, Bottom: 594}
}
func settingsHistoryExportCustomToggleRect() winRect {
return winRect{Left: 376, Top: 480, Right: 736, Bottom: 514}
return winRect{Left: 376, Top: 558, Right: 736, Bottom: 594}
}
func settingsHistoryExportCustomRect() winRect {
return winRect{Left: 116, Top: 520, Right: 636, Bottom: 544}
return winRect{Left: 24, Top: 646, Right: 636, Bottom: 678}
}
func settingsHistoryExportCustomSaveRect() winRect {
return winRect{Left: 644, Top: 520, Right: 736, Bottom: 544}
return winRect{Left: 644, Top: 646, Right: 736, Bottom: 678}
}
func handleSettingsPaneClick(app *guiApp, x, y int32, getWindowText, setWindowText *syscall.LazyProc) bool {
@@ -2043,9 +2186,9 @@ func drawSettingsRefreshControl(hdc uintptr, app *guiApp) {
drawSettingsBox(hdc, settingsRefreshValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsRefreshUpRect(), fill, border)
drawSettingsWideButton(hdc, settingsRefreshUnitRect(), "\u5358\u4f4d", refreshIntervalUnitLabel(app.refreshIntervalUnit), font, true)
drawCenteredPaneText(hdc, settingsRefreshDownRect(), "A-", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshDownRect(), "-", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshValueRect(), refreshIntervalValueLabel(app.refreshIntervalValue, app.refreshIntervalUnit), font, clrMutedText)
drawCenteredPaneText(hdc, settingsRefreshUpRect(), "A+", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshUpRect(), "+", font, clrText)
}
func refreshIntervalValueLabel(value int, unit string) string {
@@ -2670,7 +2813,7 @@ func drawSettingsFontControl(hdc uintptr, app *guiApp) {
drawSettingsBox(hdc, settingsFontValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsFontUpRect(), fill, border)
drawCenteredPaneText(hdc, settingsFontDownRect(), "A-", font, clrText)
drawCenteredPaneText(hdc, settingsFontValueRect(), strconv.Itoa(app.fontSize), font, clrMutedText)
drawCenteredPaneText(hdc, settingsFontValueRect(), strconv.Itoa(app.fontSize)+" pt", font, clrMutedText)
drawCenteredPaneText(hdc, settingsFontUpRect(), "A+", font, clrText)
}
@@ -2953,7 +3096,7 @@ func eventRows(users []userState) []guiEventRow {
}
func joinLeaveRowsForCurrentWorld(app *guiApp) []guiEventRow {
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
if len(events) == 0 {
users, _ := currentUsersFromJoinLeave()
return eventRows(users)
@@ -3116,6 +3259,7 @@ type guiWorldVisitRecord struct {
type guiWorldHistoryFile struct {
UpdatedAt string `json:"updated_at"`
Visits []guiWorldVisitRecord `json:"visits"`
Current *guiWorldVisitRecord `json:"current"`
}
func readWorldHistory() []guiWorldVisitRecord {
@@ -3141,6 +3285,9 @@ func readWorldHistory() []guiWorldVisitRecord {
return nil
}
visits = snap.Visits
if snap.Current != nil && !snap.Current.StartedAt.IsZero() {
visits = append(visits, *snap.Current)
}
}
out := dedupeWorldHistory(visits)
if info, err := os.Stat(p); err == nil {
@@ -3303,7 +3450,7 @@ func humanDurationLabel(started, ended time.Time, current bool) string {
}
d := ended.Sub(started)
if current {
return formatDurationShort(d) + " active"
return formatDurationShort(d)
}
return formatDurationShort(d)
}

View File

@@ -25,6 +25,7 @@ type WorldVisit struct {
type worldHistorySnapshot struct {
UpdatedAt time.Time `json:"updated_at"`
Visits []WorldVisit `json:"visits"`
Current *WorldVisit `json:"current,omitempty"`
}
type WorldHistoryTracker struct {
@@ -61,6 +62,10 @@ func (t *WorldHistoryTracker) load() error {
return err
}
t.visits = mergeWorldVisits(snap.Visits)
if snap.Current != nil && !snap.Current.StartedAt.IsZero() {
current := *snap.Current
t.current = &current
}
return nil
}
@@ -188,6 +193,7 @@ func (t *WorldHistoryTracker) persistLocked() error {
return enc.Encode(worldHistorySnapshot{
UpdatedAt: time.Now(),
Visits: mergeWorldVisits(t.visits),
Current: t.current,
})
}