ck/Main.go

318 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"log"
"os"
"strconv"
"time"
"unicode/utf8"
"github.com/gdamore/tcell/v2"
)
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
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)
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)
wall.SetStyle(defStyle)
wall.Clear()
drawWall()
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()
}
switch ev.Rune() {
case 'h':
moveLeft()
case 'j':
moveDown()
case 'k':
moveUp()
case 'l':
moveRight()
}
}
drawWall()
wall.Sync()
}
}