Created
April 8, 2025 16:01
-
-
Save remixer-dec/1caa798a172a76c864eb927cff540300 to your computer and use it in GitHub Desktop.
transparent stopwatch (only minutes) for mac os
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"time" | |
"github.com/progrium/darwinkit/dispatch" | |
"github.com/progrium/darwinkit/helper/action" | |
"github.com/progrium/darwinkit/helper/layout" | |
"github.com/progrium/darwinkit/macos" | |
"github.com/progrium/darwinkit/macos/appkit" | |
"github.com/progrium/darwinkit/macos/foundation" | |
"github.com/progrium/darwinkit/objc" | |
) | |
func main() { | |
macos.RunApp(launched) | |
} | |
func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { | |
// Create a small, minimal window for the stopwatch | |
w := appkit.NewWindowWithSize(150, 100) | |
objc.Retain(&w) | |
// Make the window transparent | |
w.SetBackgroundColor(appkit.ColorClass.ClearColor()) | |
w.SetOpaque(false) | |
w.SetTitleVisibility(appkit.WindowTitleHidden) | |
w.SetTitlebarAppearsTransparent(true) | |
// Variables to track stopwatch state | |
var ( | |
startTime time.Time | |
elapsedTime time.Duration | |
isRunning bool | |
stopChan chan struct{} | |
) | |
// Create the time display label - only minutes | |
timeLabel := appkit.NewLabel("") | |
timeLabel.SetFont(appkit.Font_BoldSystemFontOfSize(36)) | |
timeLabel.SetAlignment(appkit.TextAlignmentCenter) | |
timeLabel.SetTextColor(appkit.ColorClass.BlackColor()) // Ensure visibility on transparent background | |
// Create control buttons using symbols | |
startPauseButton := appkit.NewButtonWithTitle("▶️") | |
startPauseButton.SetBezelStyle(appkit.BezelStyleRounded) // Using BezelStyleRounded which we know exists | |
startPauseButton.SetFont(appkit.Font_SystemFontOfSize(16)) | |
resetButton := appkit.NewButtonWithTitle("⟲") | |
resetButton.SetBezelStyle(appkit.BezelStyleRounded) // Using BezelStyleRounded which we know exists | |
resetButton.SetFont(appkit.Font_SystemFontOfSize(16)) | |
resetButton.SetEnabled(false) | |
// Function to update display to show only minutes | |
updateMinutesDisplay := func(duration time.Duration) { | |
minutes := int(duration.Minutes()) | |
timeLabel.SetStringValue(fmt.Sprintf("%d", minutes)) | |
} | |
// Set up button actions | |
action.Set(startPauseButton, func(sender objc.Object) { | |
if isRunning { | |
// Pause the stopwatch | |
if stopChan != nil { | |
close(stopChan) | |
stopChan = nil | |
} | |
elapsedTime += time.Since(startTime) | |
startPauseButton.SetTitle("▶️") | |
resetButton.SetEnabled(true) | |
isRunning = false | |
// Update the display one final time | |
updateMinutesDisplay(elapsedTime) | |
} else { | |
// Start the stopwatch | |
startTime = time.Now() | |
startPauseButton.SetTitle("⏸️") | |
resetButton.SetEnabled(false) | |
isRunning = true | |
// Create a channel to signal when to stop the timer | |
stopChan = make(chan struct{}) | |
// Since we're only showing minutes, we can reduce update frequency | |
// Update once per second is sufficient | |
go func() { | |
ticker := time.NewTicker(1 * time.Second) | |
defer ticker.Stop() | |
for { | |
select { | |
case <-ticker.C: | |
currentElapsed := elapsedTime + time.Since(startTime) | |
dispatch.MainQueue().DispatchAsync(func() { | |
updateMinutesDisplay(currentElapsed) | |
}) | |
case <-stopChan: | |
return | |
} | |
} | |
}() | |
} | |
}) | |
action.Set(resetButton, func(sender objc.Object) { | |
// Reset the stopwatch | |
elapsedTime = 0 | |
updateMinutesDisplay(0) | |
resetButton.SetEnabled(false) | |
}) | |
// Create button container | |
buttonStackView := appkit.StackView_StackViewWithViews([]appkit.IView{ | |
startPauseButton, | |
resetButton, | |
}) | |
buttonStackView.SetOrientation(appkit.UserInterfaceLayoutOrientationHorizontal) | |
buttonStackView.SetDistribution(appkit.StackViewDistributionFillEqually) | |
buttonStackView.SetSpacing(5) | |
// Create main container for all elements | |
mainStackView := appkit.StackView_StackViewWithViews([]appkit.IView{ | |
timeLabel, | |
buttonStackView, | |
}) | |
mainStackView.SetOrientation(appkit.UserInterfaceLayoutOrientationVertical) | |
mainStackView.SetAlignment(appkit.LayoutAttributeCenterX) | |
mainStackView.SetSpacing(10) | |
// Add the main container to the window | |
w.ContentView().AddSubview(mainStackView) | |
layout.PinEdgesToSuperView(mainStackView, foundation.EdgeInsets{Top: 10, Left: 10, Bottom: 10, Right: 10}) | |
// Window configuration | |
w.MakeKeyAndOrderFront(nil) | |
w.Center() | |
// Handle window closing | |
wd := &appkit.WindowDelegate{} | |
wd.SetWindowWillClose(func(notification foundation.Notification) { | |
// Clean up timer when closing | |
if stopChan != nil { | |
close(stopChan) | |
} | |
}) | |
w.SetDelegate(wd) | |
delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { | |
return true | |
}) | |
// Configure application | |
app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) | |
app.ActivateIgnoringOtherApps(true) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment