2021-07-20 23:22:07 +02:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"log"
|
|
|
|
|
"os"
|
|
|
|
|
"strconv"
|
2021-09-04 11:50:23 +02:00
|
|
|
|
"time"
|
2021-07-20 23:22:07 +02:00
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2021-11-30 07:48:51 +01:00
|
|
|
|
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
|
2021-08-27 17:38:32 +02:00
|
|
|
|
name and weekdays included) */
|
2021-08-27 17:37:38 +02:00
|
|
|
|
|
|
|
|
|
titleHeight = smallGap + 1
|
2021-07-20 23:22:07 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
months = [...]string{"", "Јануар", "Фебруар", "Март", "Април", "Мај", "Јун",
|
|
|
|
|
"Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар"}
|
|
|
|
|
weekdays = [...]string{"Недеља", "Понедељак", "Уторак", "Среда", "Четвртак",
|
|
|
|
|
"Петак", "Субота"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
2021-11-30 07:52:05 +01:00
|
|
|
|
wall tcell.Screen
|
|
|
|
|
defStyle tcell.Style
|
|
|
|
|
todayStyle tcell.Style
|
|
|
|
|
selStyle tcell.Style
|
2021-11-30 07:53:27 +01:00
|
|
|
|
|
|
|
|
|
monthsWide int
|
|
|
|
|
monthsHigh int
|
|
|
|
|
monthsDrawn int
|
2021-07-20 23:22:07 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
selTime time.Time
|
|
|
|
|
today time.Time
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func max(a, b int) int {
|
|
|
|
|
if a > b {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 18:31:01 +02:00
|
|
|
|
func min(a, b int) int {
|
|
|
|
|
if a < b {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 23:22:07 +02:00
|
|
|
|
func date(year int, month time.Month, day int) time.Time {
|
|
|
|
|
return time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-30 07:55:36 +01:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 23:22:07 +02:00
|
|
|
|
func centeredText(s string, x, y, width int, scr tcell.Screen, style tcell.Style) {
|
2021-09-04 11:50:23 +02:00
|
|
|
|
start := x + max((width-utf8.RuneCountInString(s))/2, 0)
|
2021-07-20 23:22:07 +02:00
|
|
|
|
i := 0
|
|
|
|
|
for _, char := range s {
|
|
|
|
|
if i >= width {
|
2021-09-04 11:50:23 +02:00
|
|
|
|
break
|
2021-07-20 23:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-04 11:50:23 +02:00
|
|
|
|
scr.SetContent(start+i, y, char, nil, style)
|
2021-07-20 23:22:07 +02:00
|
|
|
|
i++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 18:33:47 +02:00
|
|
|
|
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 {
|
2021-09-04 11:50:23 +02:00
|
|
|
|
break
|
2021-08-25 18:33:47 +02:00
|
|
|
|
}
|
|
|
|
|
x = 0
|
|
|
|
|
y++
|
|
|
|
|
}
|
|
|
|
|
scr.SetContent(x, y, char, nil, style)
|
|
|
|
|
x++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-30 07:55:36 +01:00
|
|
|
|
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()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 23:22:07 +02:00
|
|
|
|
func drawMonth(m time.Month, x, y int) {
|
2021-11-30 07:48:51 +01:00
|
|
|
|
centeredText(months[m], x, y, maxMonthWidth, wall, defStyle)
|
2021-07-20 23:22:07 +02:00
|
|
|
|
|
|
|
|
|
for i := 1; i <= 7; i++ {
|
2021-09-04 11:50:23 +02:00
|
|
|
|
centeredText(weekdays[i%7], x+(i-1)*(dayWidth+smallGap), y+1, dayWidth, wall, defStyle)
|
2021-07-20 23:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-04 11:50:23 +02:00
|
|
|
|
dayNum := date(selTime.Year(), m+1, 0).Day()
|
2021-07-20 23:22:07 +02:00
|
|
|
|
for i := 1; i <= dayNum; i++ {
|
|
|
|
|
weekday := int(date(selTime.Year(), m, i).Weekday())
|
|
|
|
|
style := defStyle
|
2021-11-30 07:52:05 +01:00
|
|
|
|
if today.Day() == i && today.Month() == m {
|
|
|
|
|
style = todayStyle
|
|
|
|
|
} else if selTime.Day() == i && selTime.Month() == m {
|
2021-07-20 23:22:07 +02:00
|
|
|
|
style = selStyle
|
|
|
|
|
}
|
|
|
|
|
centeredText(
|
|
|
|
|
strconv.Itoa(i),
|
2021-09-04 11:50:23 +02:00
|
|
|
|
x+((weekday-1+7)%7)*(dayWidth+smallGap),
|
|
|
|
|
y+2,
|
2021-07-20 23:22:07 +02:00
|
|
|
|
2,
|
|
|
|
|
wall,
|
|
|
|
|
style)
|
|
|
|
|
if weekday == 0 {
|
|
|
|
|
y++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func drawWall() {
|
2021-08-28 09:39:28 +02:00
|
|
|
|
wall.Clear()
|
2021-08-27 17:39:05 +02:00
|
|
|
|
w, h := wall.Size()
|
2021-11-30 07:48:51 +01:00
|
|
|
|
if w < maxMonthWidth+2*smallGap || h < maxMonthHeight+2*smallGap+titleHeight {
|
2021-08-27 17:39:05 +02:00
|
|
|
|
wrappedText("Екран је премали за календар.", wall, defStyle)
|
|
|
|
|
return
|
|
|
|
|
}
|
2021-11-30 07:53:27 +01:00
|
|
|
|
monthsWide = (w - 2*smallGap + largeGap) / (maxMonthWidth + largeGap)
|
|
|
|
|
monthsHigh = (h - smallGap - titleHeight) / (maxMonthHeight + smallGap)
|
|
|
|
|
monthsDrawn = min(monthsHigh*monthsWide, 12)
|
2021-08-27 17:39:05 +02:00
|
|
|
|
|
2021-09-04 11:50:23 +02:00
|
|
|
|
startingMonth := time.Month((int(selTime.Month())-1)/monthsDrawn*monthsDrawn + 1)
|
|
|
|
|
endingMonth := time.Month(min(12, int(startingMonth)+monthsDrawn-1))
|
2021-08-27 17:39:05 +02:00
|
|
|
|
|
2021-11-30 07:48:51 +01:00
|
|
|
|
centerVert := (w - monthsWide*maxMonthWidth - (monthsWide-1)*largeGap) / 2
|
2021-09-04 11:50:23 +02:00
|
|
|
|
for i, m := 0, startingMonth; i < monthsHigh; i++ {
|
|
|
|
|
for j := 0; j < monthsWide; j++ {
|
2021-11-30 07:48:51 +01:00
|
|
|
|
drawMonth(m, centerVert+j*(maxMonthWidth+largeGap), titleHeight+smallGap+i*(maxMonthHeight+smallGap))
|
2021-08-27 17:39:05 +02:00
|
|
|
|
if m >= endingMonth {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
m++
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-20 23:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
today = time.Now()
|
|
|
|
|
selTime = today
|
|
|
|
|
var err error
|
|
|
|
|
wall, err = tcell.NewScreen()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("%+v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := wall.Init(); err != nil {
|
|
|
|
|
log.Fatalf("%+v", err)
|
|
|
|
|
}
|
|
|
|
|
defStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
|
2021-11-30 07:52:05 +01:00
|
|
|
|
todayStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.GetColor("blue")).Bold(true).Reverse(true)
|
2021-07-20 23:22:07 +02:00
|
|
|
|
selStyle = tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.GetColor("green")).Bold(true)
|
|
|
|
|
|
|
|
|
|
wall.SetStyle(defStyle)
|
|
|
|
|
wall.Clear()
|
2021-08-27 17:39:05 +02:00
|
|
|
|
drawWall()
|
2021-07-20 23:22:07 +02:00
|
|
|
|
|
|
|
|
|
quit := func() {
|
|
|
|
|
wall.Fini()
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
for {
|
|
|
|
|
// Update screen
|
|
|
|
|
wall.Show()
|
|
|
|
|
|
|
|
|
|
// Poll event
|
|
|
|
|
ev := wall.PollEvent()
|
|
|
|
|
|
|
|
|
|
// Process event
|
|
|
|
|
switch ev := ev.(type) {
|
|
|
|
|
case *tcell.EventResize:
|
|
|
|
|
case *tcell.EventKey:
|
|
|
|
|
if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC ||
|
|
|
|
|
ev.Rune() == 'Q' || ev.Rune() == 'q' {
|
|
|
|
|
quit()
|
|
|
|
|
}
|
2021-11-30 07:55:36 +01:00
|
|
|
|
switch ev.Rune() {
|
|
|
|
|
case 'h':
|
|
|
|
|
moveLeft()
|
|
|
|
|
case 'j':
|
|
|
|
|
moveDown()
|
|
|
|
|
case 'k':
|
|
|
|
|
moveUp()
|
|
|
|
|
case 'l':
|
|
|
|
|
moveRight()
|
|
|
|
|
}
|
2021-07-20 23:22:07 +02:00
|
|
|
|
}
|
2021-11-30 07:55:36 +01:00
|
|
|
|
|
|
|
|
|
drawWall()
|
|
|
|
|
wall.Sync()
|
2021-07-20 23:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
}
|