mirror of
https://github.com/tiennm99/time-mocker.git
synced 2026-09-14 00:18:27 +00:00
423 lines
10 KiB
Go
423 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/app"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/widget"
|
|
)
|
|
|
|
// CalendarWidget represents a custom calendar widget
|
|
type CalendarWidget struct {
|
|
widget.BaseWidget
|
|
currentTime time.Time
|
|
selectedTime time.Time
|
|
onDateSelected func(time.Time)
|
|
}
|
|
|
|
func NewCalendarWidget(initialTime time.Time, onDateSelected func(time.Time)) *CalendarWidget {
|
|
c := &CalendarWidget{
|
|
currentTime: initialTime,
|
|
selectedTime: initialTime,
|
|
onDateSelected: onDateSelected,
|
|
}
|
|
c.ExtendBaseWidget(c)
|
|
return c
|
|
}
|
|
|
|
func (c *CalendarWidget) CreateRenderer() fyne.WidgetRenderer {
|
|
c.ExtendBaseWidget(c)
|
|
return &calendarRenderer{
|
|
calendar: c,
|
|
objects: []fyne.CanvasObject{},
|
|
}
|
|
}
|
|
|
|
func (c *CalendarWidget) SetMonth(t time.Time) {
|
|
c.currentTime = t
|
|
c.Refresh()
|
|
}
|
|
|
|
func (c *CalendarWidget) PreviousMonth() {
|
|
c.currentTime = c.currentTime.AddDate(0, -1, 0)
|
|
c.Refresh()
|
|
}
|
|
|
|
func (c *CalendarWidget) NextMonth() {
|
|
c.currentTime = c.currentTime.AddDate(0, 1, 0)
|
|
c.Refresh()
|
|
}
|
|
|
|
type calendarRenderer struct {
|
|
calendar *CalendarWidget
|
|
objects []fyne.CanvasObject
|
|
}
|
|
|
|
func (r *calendarRenderer) Layout(size fyne.Size) {
|
|
// Layout is handled by the container
|
|
}
|
|
|
|
func (r *calendarRenderer) MinSize() fyne.Size {
|
|
return fyne.NewSize(350, 300)
|
|
}
|
|
|
|
func (r *calendarRenderer) Refresh() {
|
|
r.calendar.BaseWidget.Refresh()
|
|
}
|
|
|
|
func (r *calendarRenderer) Objects() []fyne.CanvasObject {
|
|
return r.objects
|
|
}
|
|
|
|
func (r *calendarRenderer) Destroy() {}
|
|
|
|
// CreateTimePicker creates a shadcn-style time picker
|
|
func CreateTimePicker(currentTime time.Time, onTimeSelected func(time.Time)) fyne.CanvasObject {
|
|
// Use pointers to allow modification in closures
|
|
hours := currentTime.Hour()
|
|
minutes := currentTime.Minute()
|
|
seconds := currentTime.Second()
|
|
|
|
// Define updateTime before the select widgets
|
|
updateTime := func() {
|
|
selectedTime := time.Date(
|
|
currentTime.Year(),
|
|
currentTime.Month(),
|
|
currentTime.Day(),
|
|
hours,
|
|
minutes,
|
|
seconds,
|
|
0,
|
|
time.Local,
|
|
)
|
|
onTimeSelected(selectedTime)
|
|
}
|
|
|
|
// Create select widgets
|
|
hourSelect := widget.NewSelect(
|
|
generateRange(0, 23),
|
|
func(value string) {
|
|
if value != "" {
|
|
var hour int
|
|
fmt.Sscanf(value, "%d", &hour)
|
|
hours = hour
|
|
updateTime()
|
|
}
|
|
},
|
|
)
|
|
hourSelect.SetSelected(fmt.Sprintf("%02d", hours))
|
|
|
|
minuteSelect := widget.NewSelect(
|
|
generateRange(0, 59),
|
|
func(value string) {
|
|
if value != "" {
|
|
var minute int
|
|
fmt.Sscanf(value, "%d", &minute)
|
|
minutes = minute
|
|
updateTime()
|
|
}
|
|
},
|
|
)
|
|
minuteSelect.SetSelected(fmt.Sprintf("%02d", minutes))
|
|
|
|
secondSelect := widget.NewSelect(
|
|
generateRange(0, 59),
|
|
func(value string) {
|
|
if value != "" {
|
|
var second int
|
|
fmt.Sscanf(value, "%d", &second)
|
|
seconds = second
|
|
updateTime()
|
|
}
|
|
},
|
|
)
|
|
secondSelect.SetSelected(fmt.Sprintf("%02d", seconds))
|
|
|
|
// Time display with colons
|
|
timeContainer := container.NewGridWithColumns(5,
|
|
hourSelect,
|
|
widget.NewLabel(":"),
|
|
minuteSelect,
|
|
widget.NewLabel(":"),
|
|
secondSelect,
|
|
)
|
|
|
|
return container.NewVBox(
|
|
widget.NewLabel("Select Time:"),
|
|
timeContainer,
|
|
)
|
|
}
|
|
|
|
func generateRange(min, max int) []string {
|
|
result := make([]string, max-min+1)
|
|
for i := range result {
|
|
result[i] = fmt.Sprintf("%02d", min+i)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// CreateCalendarView creates a calendar view widget
|
|
func CreateCalendarView(initialTime time.Time, onDateSelected func(time.Time)) fyne.CanvasObject {
|
|
currentMonth := initialTime
|
|
|
|
// Month names
|
|
months := []string{
|
|
"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December",
|
|
}
|
|
|
|
// Generate years (current year +/- 10)
|
|
currentYear := time.Now().Year()
|
|
years := make([]string, 21)
|
|
for i := 0; i < 21; i++ {
|
|
years[i] = fmt.Sprintf("%d", currentYear-10+i)
|
|
}
|
|
|
|
// Calendar grid
|
|
calendarGrid := container.NewGridWithColumns(7)
|
|
|
|
// Declare variables before creating updateCalendar
|
|
var updateCalendar func()
|
|
var prevBtn, nextBtn *widget.Button
|
|
var monthSelect, yearSelect *widget.Select
|
|
updatingCalendar := false // Flag to prevent recursive updates
|
|
|
|
// Define updateCalendar
|
|
updateCalendar = func() {
|
|
if updatingCalendar {
|
|
return
|
|
}
|
|
updatingCalendar = true
|
|
defer func() { updatingCalendar = false }()
|
|
|
|
// Update dropdowns to match current month (only if they exist)
|
|
if monthSelect != nil {
|
|
monthSelect.SetSelected(currentMonth.Format("January"))
|
|
}
|
|
if yearSelect != nil {
|
|
yearSelect.SetSelected(fmt.Sprintf("%d", currentMonth.Year()))
|
|
}
|
|
|
|
// Clear grid
|
|
calendarGrid.Objects = nil
|
|
|
|
// Add day headers
|
|
days := []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
|
|
for _, day := range days {
|
|
dayLabel := widget.NewLabel(day)
|
|
dayLabel.Alignment = fyne.TextAlignCenter
|
|
dayLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
calendarGrid.Add(dayLabel)
|
|
}
|
|
|
|
// Get first day of month and number of days
|
|
firstOfMonth := time.Date(currentMonth.Year(), currentMonth.Month(), 1, 0, 0, 0, 0, time.Local)
|
|
lastOfMonth := firstOfMonth.AddDate(0, 1, -1)
|
|
firstDayWeekday := int(firstOfMonth.Weekday())
|
|
daysInMonth := lastOfMonth.Day()
|
|
|
|
// Calculate days from previous month to fill first week
|
|
daysFromPrevMonth := firstDayWeekday
|
|
prevMonthTime := firstOfMonth.AddDate(0, -1, 0)
|
|
// Get the last day of previous month
|
|
lastDayOfPrevMonth := time.Date(prevMonthTime.Year(), prevMonthTime.Month(), 1, 0, 0, 0, 0, time.Local).AddDate(0, 1, -1).Day()
|
|
|
|
// Add days from previous month (starting from the appropriate day)
|
|
startDay := lastDayOfPrevMonth - daysFromPrevMonth + 1
|
|
for day := startDay; day <= lastDayOfPrevMonth; day++ {
|
|
dayTime := time.Date(prevMonthTime.Year(), prevMonthTime.Month(), day, 0, 0, 0, 0, time.Local)
|
|
|
|
dayBtn := widget.NewButton(fmt.Sprintf("%d", day), nil)
|
|
dayBtn.Importance = widget.LowImportance
|
|
|
|
dayBtn.OnTapped = func() {
|
|
onDateSelected(dayTime)
|
|
currentMonth = dayTime
|
|
updateCalendar()
|
|
}
|
|
|
|
calendarGrid.Add(dayBtn)
|
|
}
|
|
|
|
// Add days from current month
|
|
today := time.Now()
|
|
for day := 1; day <= daysInMonth; day++ {
|
|
dayTime := time.Date(currentMonth.Year(), currentMonth.Month(), day, 0, 0, 0, 0, time.Local)
|
|
isToday := dayTime.Year() == today.Year() &&
|
|
dayTime.Month() == today.Month() &&
|
|
dayTime.Day() == today.Day()
|
|
|
|
dayBtn := widget.NewButton(fmt.Sprintf("%d", day), nil)
|
|
|
|
if isToday {
|
|
dayBtn.Importance = widget.HighImportance
|
|
}
|
|
|
|
dayBtn.OnTapped = func() {
|
|
onDateSelected(dayTime)
|
|
}
|
|
|
|
calendarGrid.Add(dayBtn)
|
|
}
|
|
|
|
// Calculate total cells and add days from next month
|
|
totalCells := daysFromPrevMonth + daysInMonth
|
|
remainingCells := (7 - (totalCells % 7)) % 7
|
|
if remainingCells == 0 && totalCells < 42 {
|
|
remainingCells = 7
|
|
}
|
|
|
|
// Fill to complete 6 weeks (42 cells)
|
|
if totalCells+remainingCells < 42 {
|
|
remainingCells = 42 - totalCells
|
|
}
|
|
|
|
nextMonthTime := firstOfMonth.AddDate(0, 1, 0)
|
|
for day := 1; day <= remainingCells; day++ {
|
|
dayTime := time.Date(nextMonthTime.Year(), nextMonthTime.Month(), day, 0, 0, 0, 0, time.Local)
|
|
|
|
dayBtn := widget.NewButton(fmt.Sprintf("%d", day), nil)
|
|
dayBtn.Importance = widget.LowImportance
|
|
|
|
dayBtn.OnTapped = func() {
|
|
onDateSelected(dayTime)
|
|
currentMonth = dayTime
|
|
updateCalendar()
|
|
}
|
|
|
|
calendarGrid.Add(dayBtn)
|
|
}
|
|
|
|
calendarGrid.Refresh()
|
|
}
|
|
|
|
// Month dropdown
|
|
monthSelect = widget.NewSelect(months, func(value string) {
|
|
if value != "" {
|
|
for i, month := range months {
|
|
if month == value {
|
|
newMonth := time.Date(currentMonth.Year(), time.Month(i+1), 1, 0, 0, 0, 0, time.Local)
|
|
currentMonth = newMonth
|
|
updateCalendar()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
})
|
|
monthSelect.SetSelected(currentMonth.Format("January"))
|
|
|
|
// Year dropdown
|
|
yearSelect = widget.NewSelect(years, func(value string) {
|
|
if value != "" {
|
|
year, _ := strconv.Atoi(value)
|
|
newMonth := time.Date(year, currentMonth.Month(), 1, 0, 0, 0, 0, time.Local)
|
|
currentMonth = newMonth
|
|
updateCalendar()
|
|
}
|
|
})
|
|
yearSelect.SetSelected(fmt.Sprintf("%d", currentMonth.Year()))
|
|
|
|
// Navigation buttons
|
|
prevBtn = widget.NewButton("<", func() {
|
|
currentMonth = currentMonth.AddDate(0, -1, 0)
|
|
updateCalendar()
|
|
})
|
|
|
|
nextBtn = widget.NewButton(">", func() {
|
|
currentMonth = currentMonth.AddDate(0, 1, 0)
|
|
updateCalendar()
|
|
})
|
|
|
|
// Initial calendar update
|
|
updateCalendar()
|
|
|
|
// Navigation header with dropdowns
|
|
monthYearRow := container.NewBorder(nil, nil, prevBtn, nextBtn,
|
|
container.NewGridWithColumns(2, monthSelect, yearSelect),
|
|
)
|
|
|
|
// Combine everything
|
|
calendarContainer := container.NewVBox(
|
|
monthYearRow,
|
|
widget.NewSeparator(),
|
|
calendarGrid,
|
|
)
|
|
|
|
return calendarContainer
|
|
}
|
|
|
|
func main() {
|
|
a := app.New()
|
|
w := a.NewWindow("Time Changer")
|
|
w.Resize(fyne.NewSize(500, 600))
|
|
|
|
// Get current time
|
|
currentTime := time.Now()
|
|
|
|
// Selected date label
|
|
selectedDateLabel := widget.NewLabel("Selected Date:")
|
|
selectedDateLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
|
|
selectedDateValue := widget.NewLabel(currentTime.Format("2006-01-02"))
|
|
|
|
// Display label for selected datetime
|
|
selectedDateTimeLabel := widget.NewLabel(fmt.Sprintf(
|
|
"%04d-%02d-%02d %02d:%02d:%02d",
|
|
currentTime.Year(), currentTime.Month(), currentTime.Day(),
|
|
currentTime.Hour(), currentTime.Minute(), currentTime.Second(),
|
|
))
|
|
selectedDateTimeLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
|
|
// Update preview function (must be defined before it's used)
|
|
updatePreview := func() {
|
|
date, _ := time.Parse("2006-01-02", selectedDateValue.Text)
|
|
selectedDateTimeLabel.SetText(fmt.Sprintf(
|
|
"%04d-%02d-%02d %02d:%02d:%02d",
|
|
date.Year(), date.Month(), date.Day(),
|
|
currentTime.Hour(), currentTime.Minute(), currentTime.Second(),
|
|
))
|
|
}
|
|
|
|
// Calendar with date selection callback
|
|
calendar := CreateCalendarView(currentTime, func(selectedDate time.Time) {
|
|
selectedDateValue.SetText(selectedDate.Format("2006-01-02"))
|
|
currentTime = selectedDate
|
|
updatePreview()
|
|
})
|
|
|
|
// Time picker with callback
|
|
timePicker := CreateTimePicker(currentTime, func(selectedTime time.Time) {
|
|
currentTime = selectedTime
|
|
updatePreview()
|
|
})
|
|
|
|
// Set current time button
|
|
setCurrentButton := widget.NewButton("Set to Current Time", func() {
|
|
now := time.Now()
|
|
currentTime = now
|
|
selectedDateValue.SetText(now.Format("2006-01-02"))
|
|
updatePreview()
|
|
})
|
|
|
|
// Layout
|
|
content := container.NewVBox(
|
|
selectedDateLabel,
|
|
calendar,
|
|
selectedDateValue,
|
|
widget.NewSeparator(),
|
|
timePicker,
|
|
widget.NewSeparator(),
|
|
setCurrentButton,
|
|
widget.NewSeparator(),
|
|
widget.NewLabel("Preview:"),
|
|
selectedDateTimeLabel,
|
|
)
|
|
|
|
w.SetContent(content)
|
|
w.ShowAndRun()
|
|
}
|