diff --git a/calendar.go b/calendar.go new file mode 100644 index 0000000..6f3aea3 --- /dev/null +++ b/calendar.go @@ -0,0 +1,146 @@ +package main + +import ( + "fmt" + "image/color" + "strconv" + "strings" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +type calendar struct { + widget.BaseWidget + + monthPrevious *widget.Button + monthNext *widget.Button + monthLabel *widget.RichText + canvas fyne.Canvas + + day int + month int + year int + + dates *fyne.Container +} + +func daysOfMonth(c *calendar) []fyne.CanvasObject { + start, _ := time.Parse("2006-1-2", strconv.Itoa(c.year)+"-"+strconv.Itoa(c.month)+"-"+strconv.Itoa(1)) + + buttons := []fyne.CanvasObject{} + + //account for Go time pkg starting on sunday at index 0 + dayIndex := int(start.Weekday()) + if dayIndex == 0 { + dayIndex += 7 + } + + //add spacers if week doesn't start on Monday + for i := 0; i < dayIndex-1; i++ { + buttons = append(buttons, layout.NewSpacer()) + } + + for d := start; d.Month() == start.Month(); d = d.AddDate(0, 0, 1) { + + s := fmt.Sprint(d.Day()) + var b fyne.CanvasObject = widget.NewButton(s, func() { + + overlayList := c.canvas.Overlays().List() + overlayList[0].Hide() + + //functionality for task #12 "Change time using calendar and time picker affecting all city" + //to go here + fmt.Println("Date selected = "+s, c.month, c.year) + }) + + buttons = append(buttons, b) + } + + return buttons +} + +func monthYear(c *calendar) string { + return time.Month(c.month).String() + " " + strconv.Itoa(c.year) +} + +func dayMonthYear(c *calendar) string { + d, _ := time.Parse("2006-1-2", strconv.Itoa(c.year)+"-"+strconv.Itoa(c.month)+"-"+strconv.Itoa(c.day)) + return d.Weekday().String()[:3] + " " + d.Month().String() + " " + strconv.Itoa(d.Year()) +} + +func columnHeadings(textSize float32) []fyne.CanvasObject { + l := []fyne.CanvasObject{} + for i := 0; i < 7; i++ { + j := i + 1 + if j == 7 { + j = 0 + } + + var canvasObject fyne.CanvasObject = canvas.NewText(strings.ToUpper(time.Weekday(j).String()[:3]), color.NRGBA{0xFF, 0xFF, 0xFF, 0xBF}) + canvasObject.(*canvas.Text).TextSize = textSize + canvasObject.(*canvas.Text).Alignment = fyne.TextAlignCenter + l = append(l, canvasObject) + } + + return l +} + +func calendarObjects(c *calendar) []fyne.CanvasObject { + cH := columnHeadings(8) + cH = append(cH, daysOfMonth(c)...) + + return cH +} + +func newCalendarPopUpAtPos(c *calendar, canvas fyne.Canvas, pos fyne.Position) { + c.canvas = canvas + widget.ShowPopUpAtPosition(c, canvas, pos) +} + +func (c *calendar) CreateRenderer() fyne.WidgetRenderer { + + c.monthPrevious = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() { + c.month-- + if c.month < 1 { + c.month = 12 + c.year-- + } + c.monthLabel.ParseMarkdown(monthYear(c)) + + c.dates.Objects = calendarObjects(c) + }) + c.monthNext = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() { + c.month++ + if c.month > 12 { + c.month = 1 + c.year++ + } + c.monthLabel.ParseMarkdown(monthYear(c)) + + c.dates.Objects = calendarObjects(c) + }) + + c.monthLabel = widget.NewRichTextFromMarkdown(monthYear(c)) + + nav := container.New(layout.NewBorderLayout(nil, nil, c.monthPrevious, c.monthNext), + c.monthPrevious, c.monthNext, container.NewCenter(c.monthLabel)) + + c.dates = container.New(NewCalendarLayout(32), calendarObjects(c)...) + + dateContainer := container.NewVBox(nav, c.dates) + return widget.NewSimpleRenderer(dateContainer) +} + +func newCalendar() *calendar { + + c := &calendar{day: time.Now().Day(), month: int(time.Now().Month()), year: time.Now().Year()} + c.ExtendBaseWidget(c) + + return c +} diff --git a/calendarLayout.go b/calendarLayout.go new file mode 100644 index 0000000..9a09157 --- /dev/null +++ b/calendarLayout.go @@ -0,0 +1,100 @@ +package main + +import ( + "math" + + "fyne.io/fyne/v2" +) + +// Declare conformity with Layout interface +var ( + _ fyne.Layout = (*calendarLayout)(nil) + padding float32 = 0 + cellSize float64 = 32 +) + +type calendarLayout struct { + Cols int + vertical, adapt bool +} + +func NewCalendarLayout(s float64) fyne.Layout { + cellSize = s + return &calendarLayout{Cols: 7} +} + +func (g *calendarLayout) horizontal() bool { + if g.adapt { + return fyne.IsHorizontal(fyne.CurrentDevice().Orientation()) + } + + return !g.vertical +} + +func (g *calendarLayout) countRows(objects []fyne.CanvasObject) int { + count := 0 + for _, child := range objects { + if child.Visible() { + count++ + } + } + + return int(math.Ceil(float64(count) / float64(g.Cols))) +} + +// Get the leading (top or left) edge of a grid cell. +// size is the ideal cell size and the offset is which col or row its on. +func getLeading(size float64, offset int) float32 { + ret := (size + float64(padding)) * float64(offset) + + return float32(math.Round(ret)) +} + +// Get the trailing (bottom or right) edge of a grid cell. +// size is the ideal cell size and the offset is which col or row its on. +func getTrailing(size float64, offset int) float32 { + return getLeading(size, offset+1) - padding +} + +// Layout is called to pack all child objects into a specified size. +// For a GridLayout this will pack objects into a table format with the number +// of columns specified in our constructor. +func (g *calendarLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { + row, col := 0, 0 + i := 0 + for _, child := range objects { + if !child.Visible() { + continue + } + + x1 := getLeading(cellSize, col) + y1 := getLeading(cellSize, row) + x2 := getTrailing(cellSize, col) + y2 := getTrailing(cellSize, row) + + child.Move(fyne.NewPos(x1, y1)) + child.Resize(fyne.NewSize(x2-x1, y2-y1)) + + if g.horizontal() { + if (i+1)%g.Cols == 0 { + row++ + col = 0 + } else { + col++ + } + } else { + if (i+1)%g.Cols == 0 { + col++ + row = 0 + } else { + row++ + } + } + i++ + } +} + +func (g *calendarLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { + rows := g.countRows(objects) + return fyne.NewSize(float32(cellSize+float64(padding))*7, float32(cellSize+float64(padding))*float32(rows)) +} diff --git a/home.go b/home.go index 083ea96..07f3280 100644 --- a/home.go +++ b/home.go @@ -83,7 +83,7 @@ func (n *nomad) autoCompleteEntry(homeContainer *fyne.Container) *CompletionEntr n.store.list = append(n.store.list, c) n.store.save() - l := newLocation(c, n.session) + l := newLocation(c, n.session, n.main.Canvas()) homeContainer.Objects = append(homeContainer.Objects[:len(homeContainer.Objects)-1], l, homeContainer.Objects[len(homeContainer.Objects)-1]) } } @@ -106,7 +106,7 @@ func (n *nomad) makeHome() fyne.CanvasObject { cells := []fyne.CanvasObject{} for _, c := range n.store.cities() { - cells = append(cells, newLocation(c, n.session)) + cells = append(cells, newLocation(c, n.session, n.main.Canvas())) } layout := &nomadLayout{} diff --git a/location.go b/location.go index db938e0..48fc118 100644 --- a/location.go +++ b/location.go @@ -22,18 +22,19 @@ type location struct { location *city session *unsplashSession - date *widget.Select time *widget.SelectEntry button *widget.Button dots *fyne.Container + + dateButton *widget.Button + + calendar *calendar } -func newLocation(loc *city, session *unsplashSession) *location { +func newLocation(loc *city, session *unsplashSession, canvas fyne.Canvas) *location { l := &location{location: loc, session: session} l.ExtendBaseWidget(l) - l.date = widget.NewSelect([]string{}, func(string) {}) - l.date.PlaceHolder = loc.localTime.Format("Mon 02 Jan") l.time = widget.NewSelectEntry(listTimes()) l.time.PlaceHolder = "22:00" // longest l.time.Wrapping = fyne.TextWrapOff @@ -53,19 +54,25 @@ func newLocation(loc *city, session *unsplashSession) *location { l.dots = container.NewVBox(layout.NewSpacer(), l.button) + l.calendar = newCalendar() + + l.dateButton = widget.NewButton(dayMonthYear(l.calendar), func() { + newCalendarPopUpAtPos(l.calendar, canvas, fyne.NewPos(0, l.Size().Height)) + }) + l.dateButton.Alignment = widget.ButtonAlignLeading + return l } func (l *location) CreateRenderer() fyne.WidgetRenderer { bg := canvas.NewImageFromResource(theme.FileImageIcon()) bg.Translucency = 0.5 - city := widget.NewRichTextFromMarkdown("# " + strings.ToUpper(l.location.name)) - + city := widget.NewRichTextFromMarkdown("# " + l.location.name) location := canvas.NewText(" "+strings.ToUpper(l.location.country)+" ยท "+l.location.localTime.Format("MST"), locationTextColor) location.TextStyle.Monospace = true location.TextSize = 10 location.Move(fyne.NewPos(theme.Padding(), city.MinSize().Height-location.TextSize*.5)) - input := container.NewBorder(nil, nil, l.date, l.time) + input := container.NewBorder(nil, nil, l.dateButton, l.time) c := container.NewMax(bg, container.NewBorder(nil,