Skip to content

Commit

Permalink
Add icon style selector
Browse files Browse the repository at this point in the history
  • Loading branch information
pakerwreah committed Jul 27, 2023
1 parent 43d3155 commit 7358186
Show file tree
Hide file tree
Showing 23 changed files with 372 additions and 285 deletions.
4 changes: 4 additions & 0 deletions Calendr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
34091737267E81870023B76D /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34091736267E81870023B76D /* NSImage.swift */; };
3409173B267E90210023B76D /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3409173A267E90210023B76D /* Icons.swift */; };
341692C225F1AA570019E8A8 /* WallTimeScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341692C125F1AA570019E8A8 /* WallTimeScheduler.swift */; };
3417A40A2A702643005C77E4 /* StatusItemIconFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3417A4092A702643005C77E4 /* StatusItemIconFactory.swift */; };
3418207E26755CC80075B3DC /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3418207D26755CC80075B3DC /* RxSwift */; };
3418208026755CC80075B3DC /* RxTest in Frameworks */ = {isa = PBXBuildFile; productRef = 3418207F26755CC80075B3DC /* RxTest */; };
3418208426755CC80075B3DC /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 3418208326755CC80075B3DC /* RxCocoa */; };
Expand Down Expand Up @@ -174,6 +175,7 @@
34091736267E81870023B76D /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
3409173A267E90210023B76D /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = "<group>"; };
341692C125F1AA570019E8A8 /* WallTimeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WallTimeScheduler.swift; sourceTree = "<group>"; };
3417A4092A702643005C77E4 /* StatusItemIconFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemIconFactory.swift; sourceTree = "<group>"; };
341B2B3D25D06A6F00336342 /* Dropdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dropdown.swift; sourceTree = "<group>"; };
341B2B4125D0B19500336342 /* StatusItemViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemViewModelTests.swift; sourceTree = "<group>"; };
341B2B4725D0C9A600336342 /* NSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -694,6 +696,7 @@
children = (
3487A43B25E70F5B00FCC7D7 /* NextEventView.swift */,
3487A43725E706F800FCC7D7 /* NextEventViewModel.swift */,
3417A4092A702643005C77E4 /* StatusItemIconFactory.swift */,
34E004A625B61D5200241419 /* StatusItemViewModel.swift */,
);
path = MenuBar;
Expand Down Expand Up @@ -921,6 +924,7 @@
3477F3E825FD52AA008EA888 /* NSViewController+Rx.swift in Sources */,
34B5A09325B0CE6F00F7F7ED /* SettingsViewController.swift in Sources */,
3421930827999FB7002BCD36 /* TimeZone+Factory.swift in Sources */,
3417A40A2A702643005C77E4 /* StatusItemIconFactory.swift in Sources */,
345DD97326920D1B00294D90 /* CalendarViewPreview.swift in Sources */,
3492C460269FCF5B009CFAD2 /* Accessibility.swift in Sources */,
348E704B25C61B8D00B3B160 /* Radio.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Calendr/Assets/Accessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ enum Accessibility {

enum General {
static let view = "settings_general_view"
static let iconStyleDropdown = "settings_general_icon_style_dropdown"
static let dateFormatDropdown = "settings_general_date_format_dropdown"
static let dateFormatInput = "settings_general_date_format_input"
}
Expand Down
2 changes: 0 additions & 2 deletions Calendr/Assets/Generated/Strings.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,6 @@ internal enum Strings {
internal enum MenuBar {
/// Launch at login
internal static let autoLaunch = Strings.tr("Localizable", "settings.menu_bar.auto_launch", fallback: "Launch at login")
/// Date format
internal static let dateFormat = Strings.tr("Localizable", "settings.menu_bar.date_format", fallback: "Date format")
/// Custom
internal static let dateFormatCustom = Strings.tr("Localizable", "settings.menu_bar.date_format_custom", fallback: "Custom")
/// Shorten if 'notch' is present
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "Zeige Datum";
"settings.menu_bar.show_icon_date" = "Datum im Icon anzeigen";
"settings.menu_bar.show_background" = "Opaken Hintergrund anzeigen";
"settings.menu_bar.date_format" = "Datumsformat";
"settings.menu_bar.date_format_custom" = "Benutzerdefiniert";
"settings.next_event" = "Nächsten Termin";
"settings.menu_bar.show_next_event" = "Zeige nächsten Termin";
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "Show date";
"settings.menu_bar.show_icon_date" = "Show date in icon";
"settings.menu_bar.show_background" = "Show opaque background";
"settings.menu_bar.date_format" = "Date format";
"settings.menu_bar.date_format_custom" = "Custom";
"settings.next_event" = "Next Event";
"settings.menu_bar.show_next_event" = "Show next event";
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "Mostrar fecha";
"settings.menu_bar.show_icon_date" = "Mostrar fecha en icono";
"settings.menu_bar.show_background" = "Mostrar fondo opaco";
"settings.menu_bar.date_format" = "Formato de fecha";
"settings.menu_bar.date_format_custom" = "Personalizado";
"settings.next_event" = "Próximo evento";
"settings.menu_bar.show_next_event" = "Mostrar próximo evento";
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "Afficher la date";
"settings.menu_bar.show_icon_date" = "Afficher la date dans l'icône";
"settings.menu_bar.show_background" = "Afficher un fond opaque";
"settings.menu_bar.date_format" = "Format de date";
"settings.menu_bar.date_format_custom" = "Personnalisé";
"settings.next_event" = "Prochain événement";
"settings.menu_bar.show_next_event" = "Afficher prochain événement";
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "Mostra data";
"settings.menu_bar.show_icon_date" = "Mostra la data nell'icona";
"settings.menu_bar.show_background" = "Mostra sfondo opaco";
"settings.menu_bar.date_format" = "Formato della data";
"settings.menu_bar.date_format_custom" = "Personalizzato";
"settings.next_event" = "Prossimo evento";
"settings.menu_bar.show_next_event" = "Mostra il prossimo evento";
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/pt.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "Mostrar data";
"settings.menu_bar.show_icon_date" = "Mostrar data no ícone";
"settings.menu_bar.show_background" = "Mostrar fundo opaco";
"settings.menu_bar.date_format" = "Formato de data";
"settings.menu_bar.date_format_custom" = "Personalizado";
"settings.next_event" = "Próximo Evento";
"settings.menu_bar.show_next_event" = "Mostrar próximo evento";
Expand Down
1 change: 0 additions & 1 deletion Calendr/Assets/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"settings.menu_bar.show_date" = "显示日期";
"settings.menu_bar.show_icon_date" = "在图标中显示日期";
"settings.menu_bar.show_background" = "启用不透明背景";
"settings.menu_bar.date_format" = "日期格式";
"settings.menu_bar.date_format_custom" = "自定义";
"settings.next_event" = "下一个日程";
"settings.menu_bar.show_next_event" = "显示下一个日程";
Expand Down
97 changes: 49 additions & 48 deletions Calendr/Calendar/CalendarViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ class CalendarViewModel {
notificationCenter: NotificationCenter
) {

let calendarObservable = dateProvider
.calendarObservable(using: notificationCenter)
let localeChangeObservable = notificationCenter.rx
.notification(NSLocale.currentLocaleDidChangeNotification)
.void()
.startWith(())
.share(replay: 1)

let dateFormatterObservable = calendarObservable
.distinctUntilChanged()
let dateFormatterObservable = localeChangeObservable
.map {
DateFormatter(format: "MMM yyyy", calendar: $0).with(context: .beginningOfSentence)
DateFormatter(format: "MMM yyyy", calendar: dateProvider.calendar).with(context: .beginningOfSentence)
}
.share(replay: 1)

Expand All @@ -49,21 +50,21 @@ class CalendarViewModel {
.distinctUntilChanged()
.share(replay: 1)

weekDays = Observable.combineLatest(
calendarObservable, settings.highlightedWeekdays
)
.map { calendar, highlightedWeekdays in
(calendar.firstWeekday ..< calendar.firstWeekday + 7)
.map {
let weekDay = ($0 - 1) % 7
return WeekDay(
title: calendar.veryShortWeekdaySymbols[weekDay],
isHighlighted: highlightedWeekdays.contains(weekDay),
index: weekDay
)
}
}
.share(replay: 1)
weekDays = settings.highlightedWeekdays
.repeat(when: localeChangeObservable)
.map { highlightedWeekdays in
let calendar = dateProvider.calendar
return (calendar.firstWeekday ..< calendar.firstWeekday + 7)
.map {
let weekDay = ($0 - 1) % 7
return WeekDay(
title: calendar.veryShortWeekdaySymbols[weekDay],
isHighlighted: highlightedWeekdays.contains(weekDay),
index: weekDay
)
}
}
.share(replay: 1)

// Calculate date range for current month
let dateRangeObservable = dateObservable
Expand All @@ -74,35 +75,35 @@ class CalendarViewModel {
.share(replay: 1)

// Create cells for current month
let dateCellsObservable = Observable.combineLatest(
dateRangeObservable, calendarObservable
)
.map { month, calendar -> [CalendarCellViewModel] in

let monthStartWeekDay = calendar.component(.weekday, from: month.start)

let start = calendar.date(
byAdding: .day,
value: { $0 <= 0 ? $0 : $0 - 7 }(calendar.firstWeekday - monthStartWeekDay),
to: month.start
)!

return (0..<42).map { day -> CalendarCellViewModel in
let date = dateProvider.calendar.date(byAdding: .day, value: day, to: start)!
let inMonth = dateProvider.calendar.isDate(date, equalTo: month.start, toGranularity: .month)

return CalendarCellViewModel(
date: date,
inMonth: inMonth,
isToday: false,
isSelected: false,
isHovered: false,
events: [],
calendar: dateProvider.calendar
)
let dateCellsObservable = dateRangeObservable
.repeat(when: localeChangeObservable)
.map { month -> [CalendarCellViewModel] in

let calendar = dateProvider.calendar
let monthStartWeekDay = calendar.component(.weekday, from: month.start)

let start = calendar.date(
byAdding: .day,
value: { $0 <= 0 ? $0 : $0 - 7 }(calendar.firstWeekday - monthStartWeekDay),
to: month.start
)!

return (0..<42).map { day -> CalendarCellViewModel in
let date = calendar.date(byAdding: .day, value: day, to: start)!
let inMonth = calendar.isDate(date, equalTo: month.start, toGranularity: .month)

return CalendarCellViewModel(
date: date,
inMonth: inMonth,
isToday: false,
isSelected: false,
isHovered: false,
events: [],
calendar: calendar
)
}
}
}
.share(replay: 1)
.share(replay: 1)

weekNumbers = Observable.combineLatest(
settings.showWeekNumbers, dateCellsObservable
Expand Down
129 changes: 129 additions & 0 deletions Calendr/MenuBar/StatusItemIconFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// StatusItemIconFactory.swift
// Calendr
//
// Created by Paker on 25/07/23.
//

import AppKit

enum StatusItemIconFactory {

static func icon(size: CGFloat, style: StatusItemIconStyle, dateProvider: DateProviding) -> NSImage {
let headerHeight: CGFloat = 3.5
let borderWidth: CGFloat = 2
let radius: CGFloat = 2.5
let ratio: CGFloat = 1.15
let rect = CGRect(x: 0, y: 0, width: size * ratio, height: size)

let image = NSImage(size: rect.size, flipped: true) { _ in
/// can be any opaque color, but red is good for debugging
let color = NSColor.red
color.setStroke()
color.setFill()

if style != .dayOfWeek {
drawFrame(rect: rect, radius: radius, borderWidth: borderWidth, headerHeight: headerHeight)
}

switch style {

case .calendar:
drawCalendarDots(rect: rect, borderWidth: borderWidth, headerHeight: headerHeight)

case .date:
drawDate(rect: rect, headerHeight: headerHeight, dateProvider: dateProvider)

case .dayOfWeek:
drawDayOfWeekAndDate(rect: rect, dateProvider: dateProvider)
}

return true
}

image.isTemplate = true

return image
}

static func drawFrame(rect: CGRect, radius: CGFloat, borderWidth: CGFloat, headerHeight: CGFloat) {
let strokePath = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)
strokePath.addClip()
strokePath.lineWidth = borderWidth
strokePath.stroke()

var fillRect = rect
fillRect.size.height = headerHeight
let fillPath = NSBezierPath(rect: fillRect)
fillPath.fill()
}

static func drawCalendarDots(rect: CGRect, borderWidth: CGFloat, headerHeight: CGFloat) {
let offsetX: CGFloat = rect.origin.x + borderWidth
let offsetY: CGFloat = rect.origin.y + headerHeight + 0.5
let insets: CGFloat = 0.09 * rect.width
let dotSize: CGFloat = 1.4
let rows: Int = 3
let cols: Int = 4
let availableWidth: CGFloat = rect.size.width - 2 * borderWidth - 2 * insets - CGFloat(cols) * dotSize
let availableHeight: CGFloat = rect.size.height - headerHeight - borderWidth - 2 * insets - CGFloat(rows) * dotSize
let spacingX: CGFloat = availableWidth / CGFloat(cols - 1)
let spacingY: CGFloat = availableHeight / CGFloat(rows - 1)
for i in 1...10 {
let row: Int = i % rows
let col: Int = i / rows
let dotPath = NSBezierPath(
ovalIn: .init(
x: offsetX + insets + CGFloat(col) * (dotSize + spacingX),
y: offsetY + insets + CGFloat(row) * (dotSize + spacingY),
width: dotSize,
height: dotSize
)
)
dotPath.fill()
}
}

static func drawDate(rect: CGRect, headerHeight: CGFloat, dateProvider: DateProviding) {
let formatter = DateFormatter(format: "d", calendar: dateProvider.calendar)
let date = formatter.string(from: dateProvider.now)
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center
NSAttributedString(string: date, attributes: [
.font: NSFont.systemFont(ofSize: 0.6 * rect.height),
.foregroundColor: NSColor.red,
.paragraphStyle: paragraph
]).draw(in: rect.offsetBy(dx: 0, dy: rect.height / 5))
}

static func drawDayOfWeekAndDate(rect: CGRect, dateProvider: DateProviding) {
func run(fn: () -> Void) { fn() }

let formatter = DateFormatter(calendar: dateProvider.calendar)

let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center

run {
formatter.dateFormat = "E"
let date = formatter.string(from: dateProvider.now).uppercased()
NSAttributedString(string: date, attributes: [
.font: NSFont.systemFont(ofSize: 7, weight: .bold),
.foregroundColor: NSColor.red,
.paragraphStyle: paragraph
])
.draw(in: rect.offsetBy(dx: 0, dy: -1))
}

run {
formatter.dateFormat = "d"
let date = formatter.string(from: dateProvider.now)
NSAttributedString(string: date, attributes: [
.font: NSFont.systemFont(ofSize: 9.5, weight: .medium),
.foregroundColor: NSColor.red,
.paragraphStyle: paragraph
])
.draw(in: rect.offsetBy(dx: 0, dy: rect.height / 3))
}
}
}
Loading

0 comments on commit 7358186

Please sign in to comment.