mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00

committed by
Patrik Svensson

parent
0a0380ae0a
commit
3f2ca49071
274
src/Spectre.Console/Widgets/Calendar.cs
Normal file
274
src/Spectre.Console/Widgets/Calendar.cs
Normal file
@ -0,0 +1,274 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Internal;
|
||||
using Spectre.Console.Internal.Collections;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// A renderable calendar.
|
||||
/// </summary>
|
||||
public sealed class Calendar : Renderable, IHasCulture, IHasTableBorder
|
||||
{
|
||||
private const int NumberOfWeekDays = 7;
|
||||
private const int ExpectedRowCount = 6;
|
||||
|
||||
private readonly ListWithCallback<CalendarEvent> _calendarEvents;
|
||||
|
||||
private int _year;
|
||||
private int _month;
|
||||
private int _day;
|
||||
private IRenderable? _table;
|
||||
private TableBorder _border;
|
||||
private bool _useSafeBorder;
|
||||
private Style? _borderStyle;
|
||||
private bool _dirty;
|
||||
private CultureInfo _culture;
|
||||
private Style _highlightStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the calendar year.
|
||||
/// </summary>
|
||||
public int Year
|
||||
{
|
||||
get => _year;
|
||||
set => MarkAsDirty(() => _year = value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the calendar month.
|
||||
/// </summary>
|
||||
public int Month
|
||||
{
|
||||
get => _month;
|
||||
set => MarkAsDirty(() => _month = value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the calendar day.
|
||||
/// </summary>
|
||||
public int Day
|
||||
{
|
||||
get => _day;
|
||||
set => MarkAsDirty(() => _day = value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TableBorder Border
|
||||
{
|
||||
get => _border;
|
||||
set => MarkAsDirty(() => _border = value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UseSafeBorder
|
||||
{
|
||||
get => _useSafeBorder;
|
||||
set => MarkAsDirty(() => _useSafeBorder = value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Style? BorderStyle
|
||||
{
|
||||
get => _borderStyle;
|
||||
set => MarkAsDirty(() => _borderStyle = value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the calendar's <see cref="CultureInfo"/>.
|
||||
/// </summary>
|
||||
public CultureInfo Culture
|
||||
{
|
||||
get => _culture;
|
||||
set => MarkAsDirty(() => _culture = value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the calendar's highlight <see cref="Style"/>.
|
||||
/// </summary>
|
||||
public Style HightlightStyle
|
||||
{
|
||||
get => _highlightStyle;
|
||||
set => MarkAsDirty(() => _highlightStyle = value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list containing all calendar events.
|
||||
/// </summary>
|
||||
public IList<CalendarEvent> CalendarEvents => _calendarEvents;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Calendar"/> class.
|
||||
/// </summary>
|
||||
/// <param name="date">The calendar date.</param>
|
||||
public Calendar(DateTime date)
|
||||
: this(date.Year, date.Month, date.Day)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Calendar"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The calendar year.</param>
|
||||
/// <param name="month">The calendar month.</param>
|
||||
public Calendar(int year, int month)
|
||||
: this(year, month, 1)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Calendar"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The calendar year.</param>
|
||||
/// <param name="month">The calendar month.</param>
|
||||
/// <param name="day">The calendar day.</param>
|
||||
public Calendar(int year, int month, int day)
|
||||
{
|
||||
_year = year;
|
||||
_month = month;
|
||||
_day = day;
|
||||
_table = null;
|
||||
_border = TableBorder.Square;
|
||||
_useSafeBorder = true;
|
||||
_borderStyle = null;
|
||||
_dirty = true;
|
||||
_culture = CultureInfo.InvariantCulture;
|
||||
_highlightStyle = new Style(foreground: Color.Blue);
|
||||
_calendarEvents = new ListWithCallback<CalendarEvent>(() => _dirty = true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var table = GetTable();
|
||||
return table.Measure(context, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
return GetTable().Render(context, maxWidth);
|
||||
}
|
||||
|
||||
private IRenderable GetTable()
|
||||
{
|
||||
// Table needs to be built?
|
||||
if (_dirty || _table == null)
|
||||
{
|
||||
_table = BuildTable();
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
return _table;
|
||||
}
|
||||
|
||||
private IRenderable BuildTable()
|
||||
{
|
||||
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||
|
||||
var table = new Table
|
||||
{
|
||||
Border = _border,
|
||||
UseSafeBorder = _useSafeBorder,
|
||||
BorderStyle = _borderStyle,
|
||||
};
|
||||
|
||||
// Add columns
|
||||
foreach (var order in GetWeekdays())
|
||||
{
|
||||
table.AddColumn(new TableColumn(order.GetAbbreviatedDayName(culture)));
|
||||
}
|
||||
|
||||
var row = new List<IRenderable>();
|
||||
|
||||
var currentDay = 1;
|
||||
var weekday = culture.DateTimeFormat.FirstDayOfWeek;
|
||||
var weekdays = BuildWeekDayTable();
|
||||
|
||||
var daysInMonth = DateTime.DaysInMonth(Year, Month);
|
||||
while (currentDay <= daysInMonth)
|
||||
{
|
||||
if (weekdays[currentDay - 1] == weekday)
|
||||
{
|
||||
if (_calendarEvents.Any(e => e.Month == Month && e.Day == currentDay))
|
||||
{
|
||||
row.Add(new Markup(currentDay.ToString(CultureInfo.InvariantCulture) + "*", _highlightStyle));
|
||||
}
|
||||
else
|
||||
{
|
||||
row.Add(new Text(currentDay.ToString(CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
currentDay++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add empty cell
|
||||
row.Add(Text.Empty);
|
||||
}
|
||||
|
||||
if (row.Count == NumberOfWeekDays)
|
||||
{
|
||||
// Flush row
|
||||
table.AddRow(row.ToArray());
|
||||
row.Clear();
|
||||
}
|
||||
|
||||
weekday = weekday.GetNextWeekDay();
|
||||
}
|
||||
|
||||
if (row.Count > 0)
|
||||
{
|
||||
// Flush row
|
||||
table.AddRow(row.ToArray());
|
||||
row.Clear();
|
||||
}
|
||||
|
||||
// We want all calendars to have the same height.
|
||||
if (table.RowCount < ExpectedRowCount)
|
||||
{
|
||||
var diff = Math.Max(0, ExpectedRowCount - table.RowCount);
|
||||
for (var i = 0; i < diff; i++)
|
||||
{
|
||||
table.AddEmptyRow();
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private void MarkAsDirty(Action action)
|
||||
{
|
||||
action();
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
private DayOfWeek[] GetWeekdays()
|
||||
{
|
||||
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||
|
||||
var days = new DayOfWeek[7];
|
||||
days[0] = culture.DateTimeFormat.FirstDayOfWeek;
|
||||
for (var i = 1; i < 7; i++)
|
||||
{
|
||||
days[i] = days[i - 1].GetNextWeekDay();
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
private DayOfWeek[] BuildWeekDayTable()
|
||||
{
|
||||
var result = new List<DayOfWeek>();
|
||||
for (var day = 0; day < DateTime.DaysInMonth(Year, Month); day++)
|
||||
{
|
||||
result.Add(new DateTime(Year, Month, day + 1).DayOfWeek);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
54
src/Spectre.Console/Widgets/CalendarEvent.cs
Normal file
54
src/Spectre.Console/Widgets/CalendarEvent.cs
Normal file
@ -0,0 +1,54 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a calendar event.
|
||||
/// </summary>
|
||||
public sealed class CalendarEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description of the calendar event.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the year of the calendar event.
|
||||
/// </summary>
|
||||
public int Year { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the month of the calendar event.
|
||||
/// </summary>
|
||||
public int Month { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the day of the calendar event.
|
||||
/// </summary>
|
||||
public int Day { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CalendarEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="year">The year of the calendar event.</param>
|
||||
/// <param name="month">The month of the calendar event.</param>
|
||||
/// <param name="day">The day of the calendar event.</param>
|
||||
public CalendarEvent(int year, int month, int day)
|
||||
: this(string.Empty, year, month, day)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CalendarEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="description">The calendar event description.</param>
|
||||
/// <param name="year">The year of the calendar event.</param>
|
||||
/// <param name="month">The month of the calendar event.</param>
|
||||
/// <param name="day">The day of the calendar event.</param>
|
||||
public CalendarEvent(string description, int year, int month, int day)
|
||||
{
|
||||
Description = description ?? string.Empty;
|
||||
Year = year;
|
||||
Month = month;
|
||||
Day = day;
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,29 @@ namespace Spectre.Console
|
||||
_items = new List<IRenderable>(items.Select(item => new Markup(item)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var maxPadding = Math.Max(Padding.Left, Padding.Right);
|
||||
|
||||
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
|
||||
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
|
||||
|
||||
var rows = _items.Count / columnCount;
|
||||
var greatestWidth = 0;
|
||||
for (var row = 0; row < rows; row += columnCount)
|
||||
{
|
||||
var widths = itemWidths.Skip(row * columnCount).Take(columnCount).ToList();
|
||||
var totalWidth = widths.Sum() + (maxPadding * (widths.Count - 1));
|
||||
if (totalWidth > greatestWidth)
|
||||
{
|
||||
greatestWidth = totalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return new Measurement(greatestWidth, greatestWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
|
Reference in New Issue
Block a user