using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.Media;
// Avalonia's Button, not Android.Widget's. .NET for Android puts Android.Widget in this project's
// implicit usings, so the bare name is ambiguous — the same collision the namespace itself causes
// for Android.App. See the note at the top of MainActivity.
using Application = Avalonia.Application;
using Button = Avalonia.Controls.Button;
namespace DodoSSH.Client.Android.Views;
///
/// One destination in the bottom bar.
///
///
///
/// Written in C# rather than as a XAML control because it is a button with a glyph, a label and a rule about
/// the accent bar, and a templated control for that would be more machinery than the thing it draws.
///
///
/// The accent bar is on top, not underneath. The v2 design distinguishes the current destination with
/// colour and weight alone and draws no bar at all; the bar is kept, and kept at the top, for a reason the
/// design does not have to think about. The bottom of a phone screen is under the system's own gesture bar,
/// so an indicator drawn there is one the user cannot see — and colour alone at 9pt on a dark background is
/// the single signal a user with a colour deficiency would be left with. Weight, colour and the bar are
/// three, which is the right number for the control that says where you are.
///
///
/// Every colour here is resolved from Theme/Palette.axaml rather than written out. A nav bar that
/// named its own blues is exactly how the fourth place a colour appears ends up one shade off — which is the
/// argument the desktop head's Ellipse.dot style makes for being a style rather than a converter.
///
///
internal sealed class NavButton : Button
{
public static readonly StyledProperty LabelProperty =
AvaloniaProperty.Register(nameof(Label), string.Empty);
/// The glyph drawn above the label.
///
/// A character rather than an icon asset. The four the design uses — ▤ ⌗ ⚿ ≣ — are all in the fonts
/// Android already has, so shipping four vectors to draw them would buy nothing but four more files to
/// keep in step with the palette.
///
public static readonly StyledProperty GlyphProperty =
AvaloniaProperty.Register(nameof(Glyph), string.Empty);
public static readonly StyledProperty IsCurrentProperty =
AvaloniaProperty.Register(nameof(IsCurrent));
private readonly Border indicator;
private readonly TextBlock glyph;
private readonly TextBlock text;
// Resolved from the palette once the control is in the tree; see Repaint.
private IBrush? currentBrush;
private IBrush? idleBrush;
public NavButton()
{
Background = Brushes.Transparent;
BorderThickness = new Thickness(0);
CornerRadius = new CornerRadius(0);
Padding = new Thickness(0);
HorizontalAlignment = HorizontalAlignment.Stretch;
VerticalAlignment = VerticalAlignment.Stretch;
HorizontalContentAlignment = HorizontalAlignment.Stretch;
VerticalContentAlignment = VerticalAlignment.Stretch;
indicator = new Border
{
Height = 2,
VerticalAlignment = VerticalAlignment.Top,
Background = Brushes.Transparent,
};
glyph = new TextBlock
{
FontSize = 15,
HorizontalAlignment = HorizontalAlignment.Center,
};
text = new TextBlock
{
FontSize = 9,
FontWeight = FontWeight.Medium,
HorizontalAlignment = HorizontalAlignment.Center,
};
var stack = new StackPanel
{
Spacing = 3,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Children = { glyph, text },
};
Content = new Panel { Children = { indicator, stack } };
}
public string Label
{
get => GetValue(LabelProperty);
set => SetValue(LabelProperty, value);
}
///
public string Glyph
{
get => GetValue(GlyphProperty);
set => SetValue(GlyphProperty, value);
}
/// Whether this is the destination currently showing.
public bool IsCurrent
{
get => GetValue(IsCurrentProperty);
set => SetValue(IsCurrentProperty, value);
}
///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == LabelProperty)
{
text.Text = Label;
}
else if (change.Property == GlyphProperty)
{
glyph.Text = Glyph;
}
else if (change.Property == IsCurrentProperty)
{
Repaint();
}
else if (change.Property == FontFamilyProperty)
{
text.FontFamily = FontFamily;
}
}
///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
// Resolved here rather than in the constructor: application resources are not reachable while the
// control is still being constructed, and a hard-coded stack would drift from Palette.axaml.
if (Application.Current?.FindResource("MonoFont") is FontFamily mono)
{
text.FontFamily = mono;
}
currentBrush = Application.Current?.FindResource("AccentText") as IBrush;
idleBrush = Application.Current?.FindResource("TextFaint") as IBrush;
// IsCurrent is normally bound and so has usually already been set by the time the template applies,
// which is why this runs again here rather than only on the property change.
Repaint();
}
private void Repaint()
{
// Both the label and the bar, not just the bar — and the weight as well. See the remark on the type.
var paint = IsCurrent ? currentBrush : idleBrush;
if (paint is not null)
{
glyph.Foreground = paint;
text.Foreground = paint;
}
text.FontWeight = IsCurrent ? FontWeight.SemiBold : FontWeight.Medium;
indicator.Background = IsCurrent && currentBrush is not null ? currentBrush : Brushes.Transparent;
}
}