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() }