diff --git a/Main.go b/Main.go index 8a87602..5213f30 100644 --- a/Main.go +++ b/Main.go @@ -3,293 +3,43 @@ package main import ( "log" "os" - "strconv" "time" - "unicode/utf8" "github.com/gdamore/tcell/v2" + + "gitlab.com/tvrdosrz/ck/drw" + "gitlab.com/tvrdosrz/ck/util" ) -const ( - dayWidth = 2 - smallGap = 1 - largeGap = 3 - maxMonthWidth = 7*dayWidth + 6*smallGap - maxMonthHeight = (31+6+(7-1))/7 + 2 - /* maxMonthHeight is the height of a 31-day month starting on Sunday (month - name and weekdays included) */ - - titleHeight = smallGap + 1 -) - -var ( - months = [...]string{"", "Јануар", "Фебруар", "Март", "Април", "Мај", "Јун", - "Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар"} - weekdays = [...]string{"Недеља", "Понедељак", "Уторак", "Среда", "Четвртак", - "Петак", "Субота"} -) - -var ( - wall tcell.Screen - defStyle tcell.Style - todayStyle tcell.Style - selStyle tcell.Style - - monthsWide int - monthsHigh int - monthsDrawn int -) - -var ( - selTime time.Time - today time.Time -) - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func date(year int, month time.Month, day int) time.Time { - return time.Date(year, month, day, 0, 0, 0, 0, time.Local) -} - -func lastDay(m time.Month) time.Time { - return date(selTime.Year(), m+1, 0) -} - -func monthHeight(m time.Month) int { - return (offset(m) + lastDay(m).Day() - 1) / 7 -} - -func centeredText(s string, x, y, width int, scr tcell.Screen, style tcell.Style) { - start := x + max((width-utf8.RuneCountInString(s))/2, 0) - i := 0 - for ; x+i < start; i++ { - scr.SetContent(x+i, y, ' ', nil, style) - } - for _, char := range s { - if i >= width { - break - } - - scr.SetContent(x+i, y, char, nil, style) - i++ - } - for ; x+i < start; i++ { - scr.SetContent(x+i, y, ' ', nil, style) - } -} - -func wrappedText(s string, scr tcell.Screen, style tcell.Style) { - x := 0 - y := 0 - w, h := scr.Size() - for _, char := range s { - if x >= w { - if y >= h { - break - } - x = 0 - y++ - } - scr.SetContent(x, y, char, nil, style) - x++ - } -} - -func bottomOfMonth(m time.Month, w time.Weekday) (time time.Time) { - last := lastDay(m) - day := last.Day() - - if w > last.Weekday() { - day -= 7 - } - - day += int(w - last.Weekday()) - - time = date(selTime.Year(), m, day) - return -} - -func topOfMonth(m time.Month, w time.Weekday) (time time.Time) { - first := date(selTime.Year(), m, 1) - day := 1 - - if w < first.Weekday() { - day += 7 - } - - day += int(w - first.Weekday()) - - time = date(selTime.Year(), m, day) - return -} - -func moveUp() { - weekday := selTime.Weekday() - newTime := selTime.AddDate(0, 0, -7) - if newTime.Month() == selTime.Month() { - selTime = newTime - } else if int(selTime.Month()) > monthsWide { - newMonth := selTime.Month() - time.Month(monthsWide) - selTime = bottomOfMonth(newMonth, weekday) - } -} - -func moveDown() { - weekday := selTime.Weekday() - newTime := selTime.AddDate(0, 0, +7) - if newTime.Month() == selTime.Month() { - selTime = newTime - } else if int(selTime.Month()) <= (12-1)/monthsWide*monthsWide { - newMonth := selTime.Month() + time.Month(monthsWide) - selTime = topOfMonth(newMonth, weekday) - } -} - -func offset(m time.Month) int { - return (int(date(selTime.Year(), m, 1).Weekday()) + 6) % 7 - -} - -func rowInMonth(m time.Month, day int) int { - return (day + offset(m) - 1) / 7 -} - -func leftmostInRow(m time.Month, row int) time.Time { - if row == 0 { - return date(selTime.Year(), m, 1) - } - if row > monthHeight(m) { - row = monthHeight(m) - } - - day := row*7 + 1 - offset(m) - - return date(selTime.Year(), m, day) -} - -func rightmostInRow(m time.Month, row int) time.Time { - if row >= monthHeight(m) { - return lastDay(m) - } - - day := row*7 + 7 - offset(m) - - return date(selTime.Year(), m, day) -} - -func moveRight() { - if selTime.Weekday() != time.Sunday && selTime.Day() != lastDay(selTime.Month()).Day() { - selTime = selTime.AddDate(0, 0, 1) - } else if int(selTime.Month())%monthsWide != 0 { - selTime = leftmostInRow(selTime.Month()+1, rowInMonth(selTime.Month(), selTime.Day())) - } -} - -func moveLeft() { - if selTime.Weekday() != time.Monday && selTime.Day() != 1 { - selTime = selTime.AddDate(0, 0, -1) - } else if int(selTime.Month())%monthsWide != 1 && monthsWide != 1 { // special case, if monthsWide - // is 1, (x%monthsWide) is 0 - selTime = rightmostInRow(selTime.Month()-1, rowInMonth(selTime.Month(), selTime.Day())) - } -} - -func drawMonth(m time.Month, x, y int) { - centeredText(months[m], x, y, maxMonthWidth, wall, defStyle) - - for i := 1; i <= 7; i++ { - centeredText(weekdays[i%7], x+(i-1)*(dayWidth+smallGap), y+1, dayWidth, wall, defStyle) - } - - dayNum := date(selTime.Year(), m+1, 0).Day() - for i := 1; i <= dayNum; i++ { - weekday := int(date(selTime.Year(), m, i).Weekday()) - style := defStyle - if today.Day() == i && today.Month() == m { - style = todayStyle - } else if selTime.Day() == i && selTime.Month() == m { - style = selStyle - } - centeredText( - strconv.Itoa(i), - x+((weekday-1+7)%7)*(dayWidth+smallGap), - y+2, - 2, - wall, - style) - if weekday == 0 { - y++ - } - } -} - -func drawWall() { - wall.Clear() - w, h := wall.Size() - if w < maxMonthWidth+2*smallGap || h < maxMonthHeight+2*smallGap+titleHeight { - wrappedText("Екран је премали за календар.", wall, defStyle) - return - } - monthsWide = (w - 2*smallGap + largeGap) / (maxMonthWidth + largeGap) - monthsHigh = (h - smallGap - titleHeight) / (maxMonthHeight + smallGap) - monthsDrawn = min(monthsHigh*monthsWide, 12) - - startingMonth := time.Month((int(selTime.Month())-1)/monthsDrawn*monthsDrawn + 1) - endingMonth := time.Month(min(12, int(startingMonth)+monthsDrawn-1)) - - centerVert := (w - monthsWide*maxMonthWidth - (monthsWide-1)*largeGap) / 2 - for i, m := 0, startingMonth; i < monthsHigh; i++ { - for j := 0; j < monthsWide; j++ { - drawMonth(m, centerVert+j*(maxMonthWidth+largeGap), titleHeight+smallGap+i*(maxMonthHeight+smallGap)) - if m >= endingMonth { - return - } - m++ - } - } -} - func main() { - today = time.Now() - selTime = today + util.Today = time.Now() + util.SelTime = util.Today var err error - wall, err = tcell.NewScreen() + drw.Wall, err = tcell.NewScreen() if err != nil { log.Fatalf("%+v", err) } - if err := wall.Init(); err != nil { + if err := drw.Wall.Init(); err != nil { log.Fatalf("%+v", err) } - defStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) - todayStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.GetColor("blue")).Bold(true).Reverse(true) - selStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.GetColor("green")).Bold(true) + drw.DefStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) + drw.TodayStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.GetColor("blue")).Bold(true).Reverse(true) + drw.SelStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.GetColor("green")).Bold(true) - wall.SetStyle(defStyle) - wall.Clear() - drawWall() + drw.Wall.SetStyle(drw.DefStyle) + drw.Wall.Clear() + drw.DrawWall() quit := func() { - wall.Fini() + drw.Wall.Fini() os.Exit(0) } for { // Update screen - wall.Show() + drw.Wall.Show() // Poll event - ev := wall.PollEvent() + ev := drw.Wall.PollEvent() // Process event switch ev := ev.(type) { @@ -301,17 +51,17 @@ func main() { } switch ev.Rune() { case 'h': - moveLeft() + drw.MoveLeft() case 'j': - moveDown() + drw.MoveDown() case 'k': - moveUp() + drw.MoveUp() case 'l': - moveRight() + drw.MoveRight() } } - drawWall() - wall.Sync() + drw.DrawWall() + drw.Wall.Sync() } } diff --git a/drw/drw.go b/drw/drw.go new file mode 100644 index 0000000..7d13d4c --- /dev/null +++ b/drw/drw.go @@ -0,0 +1,57 @@ +package drw + +import ( + "unicode/utf8" + + "github.com/gdamore/tcell/v2" + "gitlab.com/tvrdosrz/ck/util" +) + +var ( + months = [...]string{"", "Јануар", "Фебруар", "Март", "Април", "Мај", "Јун", + "Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар"} + weekdays = [...]string{"Недеља", "Понедељак", "Уторак", "Среда", "Четвртак", + "Петак", "Субота"} +) + +var ( + DefStyle tcell.Style + TodayStyle tcell.Style + SelStyle tcell.Style +) + +func centeredText(s string, x, y, width int, scr tcell.Screen, style tcell.Style) { + start := x + util.Max((width-utf8.RuneCountInString(s))/2, 0) + i := 0 + for ; x+i < start; i++ { + scr.SetContent(x+i, y, ' ', nil, style) + } + for _, char := range s { + if i >= width { + break + } + + scr.SetContent(x+i, y, char, nil, style) + i++ + } + for ; x+i < start; i++ { + scr.SetContent(x+i, y, ' ', nil, style) + } +} + +func wrappedText(s string, scr tcell.Screen, style tcell.Style) { + x := 0 + y := 0 + w, h := scr.Size() + for _, char := range s { + if x >= w { + if y >= h { + break + } + x = 0 + y++ + } + scr.SetContent(x, y, char, nil, style) + x++ + } +} diff --git a/drw/wall.go b/drw/wall.go new file mode 100644 index 0000000..84b2af3 --- /dev/null +++ b/drw/wall.go @@ -0,0 +1,192 @@ +package drw + +import ( + "strconv" + "time" + + "github.com/gdamore/tcell/v2" + + "gitlab.com/tvrdosrz/ck/util" +) + +const ( + dayWidth = 2 + smallGap = 1 + largeGap = 3 + maxMonthWidth = 7*dayWidth + 6*smallGap + maxMonthHeight = (31+6+(7-1))/7 + 2 + /* maxMonthHeight is the height of a 31-day month starting on Sunday (month + name and weekdays included) */ + + titleHeight = smallGap + 1 +) + +var ( + Wall tcell.Screen + + monthsWide int + monthsHigh int + monthsDrawn int +) + +func offset(m time.Month) int { + return (int(util.Date(util.SelTime.Year(), m, 1).Weekday()) + 6) % 7 + +} + +func lastDay(m time.Month) time.Time { + return util.Date(util.SelTime.Year(), m+1, 0) +} + +func monthHeight(m time.Month) int { + return (offset(m) + lastDay(m).Day() - 1) / 7 +} + +func bottomOfMonth(m time.Month, w time.Weekday) (time time.Time) { + last := lastDay(m) + day := last.Day() + + if w > last.Weekday() { + day -= 7 + } + + day += int(w - last.Weekday()) + + time = util.Date(util.SelTime.Year(), m, day) + return +} + +func topOfMonth(m time.Month, w time.Weekday) (time time.Time) { + first := util.Date(util.SelTime.Year(), m, 1) + day := 1 + + if w < first.Weekday() { + day += 7 + } + + day += int(w - first.Weekday()) + + time = util.Date(util.SelTime.Year(), m, day) + return +} + +func rowInMonth(m time.Month, day int) int { + return (day + offset(m) - 1) / 7 +} + +func leftmostInRow(m time.Month, row int) time.Time { + if row == 0 { + return util.Date(util.SelTime.Year(), m, 1) + } + if row > monthHeight(m) { + row = monthHeight(m) + } + + day := row*7 + 1 - offset(m) + + return util.Date(util.SelTime.Year(), m, day) +} + +func rightmostInRow(m time.Month, row int) time.Time { + if row >= monthHeight(m) { + return lastDay(m) + } + + day := row*7 + 7 - offset(m) + + return util.Date(util.SelTime.Year(), m, day) +} + +func MoveUp() { + weekday := util.SelTime.Weekday() + newTime := util.SelTime.AddDate(0, 0, -7) + if newTime.Month() == util.SelTime.Month() { + util.SelTime = newTime + } else if int(util.SelTime.Month()) > monthsWide { + newMonth := util.SelTime.Month() - time.Month(monthsWide) + util.SelTime = bottomOfMonth(newMonth, weekday) + } +} + +func MoveDown() { + weekday := util.SelTime.Weekday() + newTime := util.SelTime.AddDate(0, 0, +7) + if newTime.Month() == util.SelTime.Month() { + util.SelTime = newTime + } else if int(util.SelTime.Month()) <= (12-1)/monthsWide*monthsWide { + newMonth := util.SelTime.Month() + time.Month(monthsWide) + util.SelTime = topOfMonth(newMonth, weekday) + } +} + +func MoveRight() { + if util.SelTime.Weekday() != time.Sunday && util.SelTime.Day() != lastDay(util.SelTime.Month()).Day() { + util.SelTime = util.SelTime.AddDate(0, 0, 1) + } else if int(util.SelTime.Month())%monthsWide != 0 { + util.SelTime = leftmostInRow(util.SelTime.Month()+1, rowInMonth(util.SelTime.Month(), util.SelTime.Day())) + } +} + +func MoveLeft() { + if util.SelTime.Weekday() != time.Monday && util.SelTime.Day() != 1 { + util.SelTime = util.SelTime.AddDate(0, 0, -1) + } else if int(util.SelTime.Month())%monthsWide != 1 && monthsWide != 1 { // special case, if monthsWide + // is 1, (x%monthsWide) is 0 + util.SelTime = rightmostInRow(util.SelTime.Month()-1, rowInMonth(util.SelTime.Month(), util.SelTime.Day())) + } +} + +func drawMonth(m time.Month, x, y int) { + centeredText(months[m], x, y, maxMonthWidth, Wall, DefStyle) + + for i := 1; i <= 7; i++ { + centeredText(weekdays[i%7], x+(i-1)*(dayWidth+smallGap), y+1, dayWidth, Wall, DefStyle) + } + + dayNum := util.Date(util.SelTime.Year(), m+1, 0).Day() + for i := 1; i <= dayNum; i++ { + weekday := int(util.Date(util.SelTime.Year(), m, i).Weekday()) + style := DefStyle + if util.Today.Day() == i && util.Today.Month() == m { + style = TodayStyle + } else if util.SelTime.Day() == i && util.SelTime.Month() == m { + style = SelStyle + } + centeredText( + strconv.Itoa(i), + x+((weekday-1+7)%7)*(dayWidth+smallGap), + y+2, + 2, + Wall, + style) + if weekday == 0 { + y++ + } + } +} + +func DrawWall() { + Wall.Clear() + w, h := Wall.Size() + if w < maxMonthWidth+2*smallGap || h < maxMonthHeight+2*smallGap+titleHeight { + wrappedText("Екран је премали за календар.", Wall, DefStyle) + return + } + monthsWide = (w - 2*smallGap + largeGap) / (maxMonthWidth + largeGap) + monthsHigh = (h - smallGap - titleHeight) / (maxMonthHeight + smallGap) + monthsDrawn = util.Min(monthsHigh*monthsWide, 12) + + startingMonth := time.Month((int(util.SelTime.Month())-1)/monthsDrawn*monthsDrawn + 1) + endingMonth := time.Month(util.Min(12, int(startingMonth)+monthsDrawn-1)) + + centerVert := (w - monthsWide*maxMonthWidth - (monthsWide-1)*largeGap) / 2 + for i, m := 0, startingMonth; i < monthsHigh; i++ { + for j := 0; j < monthsWide; j++ { + drawMonth(m, centerVert+j*(maxMonthWidth+largeGap), titleHeight+smallGap+i*(maxMonthHeight+smallGap)) + if m >= endingMonth { + return + } + m++ + } + } +} diff --git a/util/cal.go b/util/cal.go new file mode 100644 index 0000000..58281a2 --- /dev/null +++ b/util/cal.go @@ -0,0 +1,12 @@ +package util + +import "time" + +var ( + SelTime time.Time + Today time.Time +) + +func Date(year int, month time.Month, day int) time.Time { + return time.Date(year, month, day, 0, 0, 0, 0, time.Local) +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..92b2fe7 --- /dev/null +++ b/util/util.go @@ -0,0 +1,15 @@ +package util + +func Max(a, b int) int { + if a > b { + return a + } + return b +} + +func Min(a, b int) int { + if a < b { + return a + } + return b +}