Item name "..."
This commit is contained in:
@@ -135,6 +135,7 @@
|
||||
<converters:ItemViewModelIsAttibuteTypeConverter x:Key="ItemViewModelIsNotAttibuteTypeConverter" Invert="true"/>
|
||||
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/>
|
||||
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter"/>
|
||||
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
@@ -158,11 +159,14 @@
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView > ListBoxItem">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="ContextMenu">
|
||||
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
|
||||
</Setter>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Avalonia.Views;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class NamePartShrinkerConverter : IMultiValueConverter
|
||||
{
|
||||
private const int PixelPerChar = 8;
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
|
||||
{
|
||||
var newNameParts = nameParts;
|
||||
//if (values.Count > 1 && values[1] is ItemView itemView && itemView.ShowAttributes && itemView.Bounds.Width > 0)
|
||||
if (values.Count > 1 && values[1] is double width && width > 0)
|
||||
{
|
||||
newNameParts = GetNamePartsForWidth(nameParts, width - 340);
|
||||
}
|
||||
|
||||
return newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<ItemNamePart> GetNamePartsForWidth(IList<ItemNamePart> nameParts, double maxWidth)
|
||||
{
|
||||
//Best case, we are in the range
|
||||
var textLength = nameParts.Select(p => p.Text.Length).Sum();
|
||||
if (textLength * PixelPerChar <= maxWidth)
|
||||
{
|
||||
return nameParts.ToList();
|
||||
}
|
||||
|
||||
//Trying at least with the special parts
|
||||
var newNameParts = new ItemNamePart?[nameParts.Count];
|
||||
for (var i = 0; i < nameParts.Count; i++)
|
||||
{
|
||||
if (nameParts[i].IsSpecial)
|
||||
{
|
||||
newNameParts[i] = nameParts[i];
|
||||
}
|
||||
}
|
||||
|
||||
return GetNamePartsForWidthOptimistic(nameParts, newNameParts, maxWidth);
|
||||
}
|
||||
|
||||
private static List<ItemNamePart> GetNamePartsForWidthOptimistic(IList<ItemNamePart> nameParts, ItemNamePart?[] newNameParts, double maxWidth)
|
||||
{
|
||||
var trimmedIndexes = new List<int>();
|
||||
for (var i = 0; i < newNameParts.Length; i++)
|
||||
{
|
||||
if (newNameParts[i] == null)
|
||||
{
|
||||
trimmedIndexes.Add(i);
|
||||
newNameParts[i] = new ItemNamePart("...");
|
||||
}
|
||||
}
|
||||
|
||||
var textLength = newNameParts.Select(p => p?.Text.Length ?? 0).Sum();
|
||||
if (textLength * PixelPerChar > maxWidth)
|
||||
{
|
||||
return GetNamePartsForWidthPessimistic(nameParts, maxWidth);
|
||||
}
|
||||
|
||||
foreach (var trimmedIndex in trimmedIndexes)
|
||||
{
|
||||
var baseTextLength = newNameParts.Select((p, i) => i == trimmedIndex ? 0 : (p?.Text.Length ?? 0)).Sum();
|
||||
var proposedText = nameParts[trimmedIndex].Text;
|
||||
var trimmed = false;
|
||||
while ((baseTextLength + proposedText.Length + (trimmed ? 3 : 0)) * PixelPerChar > maxWidth)
|
||||
{
|
||||
proposedText = proposedText[0..^1];
|
||||
trimmed = true;
|
||||
}
|
||||
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
|
||||
if (trimmed) break;
|
||||
}
|
||||
|
||||
return newNameParts.OfType<ItemNamePart>().ToList();
|
||||
}
|
||||
|
||||
private static List<ItemNamePart> GetNamePartsForWidthPessimistic(IList<ItemNamePart> nameParts, double maxWidth)
|
||||
{
|
||||
var newNameParts = new List<ItemNamePart>(nameParts);
|
||||
foreach (var namePart in nameParts)
|
||||
{
|
||||
var baseTextLength = newNameParts.Select(p => p.Text.Length).Sum();
|
||||
var proposedText = namePart.Text;
|
||||
var trimmed = false;
|
||||
|
||||
while ((baseTextLength + 3) * PixelPerChar > maxWidth && proposedText != "")
|
||||
{
|
||||
proposedText = proposedText[0..^1];
|
||||
trimmed = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
|
||||
if (trimmed) break;
|
||||
}
|
||||
if (newNameParts.Last().IsSpecial)
|
||||
{
|
||||
newNameParts.Add(new ItemNamePart("..."));
|
||||
}
|
||||
else
|
||||
{
|
||||
var last = newNameParts.Last();
|
||||
last.Text += "...";
|
||||
}
|
||||
return newNameParts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationIcon>Assets\filetime.ico</ApplicationIcon>
|
||||
<Version>0.0.1</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
using Avalonia.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.Models
|
||||
namespace FileTime.Avalonia.Models
|
||||
{
|
||||
public class ItemNamePart
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public TextDecorationCollection? TextDecorations { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
|
||||
public ItemNamePart(string text)
|
||||
public ItemNamePart(string text, bool isSpecial = false)
|
||||
{
|
||||
Text = text;
|
||||
IsSpecial = isSpecial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace FileTime.Avalonia.Services
|
||||
{Commands.CreateElement, CreateElement},
|
||||
{Commands.Cut, Cut},
|
||||
{Commands.EnterRapidTravel, EnterRapidTravelMode},
|
||||
{Commands.Edit, Edit},
|
||||
{Commands.GoToHome, GotToHome},
|
||||
{Commands.GoToPath, GoToContainer},
|
||||
{Commands.GoToProvider, GotToProvider},
|
||||
@@ -779,5 +780,10 @@ namespace FileTime.Avalonia.Services
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Edit()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace FileTime.Avalonia.Services
|
||||
nameParts.Add(new ItemNamePart(before));
|
||||
}
|
||||
|
||||
nameParts.Add(new ItemNamePart(rapidTravel) { TextDecorations = TextDecorations.Underline });
|
||||
nameParts.Add(new ItemNamePart(rapidTravel, true));
|
||||
}
|
||||
|
||||
if (nameLeft.Length > 0)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
public class ItemNamePartViewModel
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public TextDecorationCollection? TextDecorations { get; set; }
|
||||
|
||||
public ItemNamePartViewModel(string text, TextDecorationCollection? textDecorations)
|
||||
{
|
||||
Text = text;
|
||||
TextDecorations = textDecorations;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ using FileTime.Avalonia.IconProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading;
|
||||
using Avalonia.Input;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -52,9 +53,24 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private bool _loading = true;
|
||||
|
||||
public string Title { get; private set; }
|
||||
|
||||
async partial void OnInitialize()
|
||||
{
|
||||
_logger?.LogInformation($"Starting {nameof(MainPageViewModel)} initialization...");
|
||||
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var versionString = "Unknwon version";
|
||||
if (version != null)
|
||||
{
|
||||
versionString = $"{version.Major}.{version.Minor}.{version.Build}";
|
||||
if (version.Revision != 0)
|
||||
{
|
||||
versionString += $" ({version.Revision})";
|
||||
}
|
||||
}
|
||||
Title = "FileTime " + versionString;
|
||||
|
||||
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
|
||||
var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService<IInputInterface>()!;
|
||||
inputInterface.InputHandler = DialogService.ReadInputs;
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="FileTime.Avalonia.Views.ItemView"
|
||||
xmlns:models="using:FileTime.Avalonia.Models"
|
||||
x:Name="ItemRoot" Background="{Binding ViewMode,Converter={StaticResource ItemViewModeToBackgroundConverter}}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="3">
|
||||
x:Name="ItemRoot"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{Binding ViewMode,Converter={StaticResource ItemViewModeToBackgroundConverter}}">
|
||||
<Grid ColumnDefinitions="20,*,300" Margin="3" x:Name="RootGrid">
|
||||
<Grid.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{Binding DataContext.ViewMode,Converter={StaticResource ItemViewModeToForegroundConverter},ElementName=ItemRoot}"/>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Image
|
||||
Grid.RowSpan="2"
|
||||
Width="18"
|
||||
Height="18"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -24,7 +26,15 @@
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Items="{Binding DisplayName}">
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.Items>
|
||||
<MultiBinding Converter="{StaticResource NamePartShrinkerConverter}">
|
||||
<MultiBinding.Bindings>
|
||||
<Binding Path="DisplayName"/>
|
||||
<Binding ElementName="RootGrid" Path="Bounds.Width"/>
|
||||
</MultiBinding.Bindings>
|
||||
</MultiBinding>
|
||||
</ItemsControl.Items>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
@@ -42,7 +52,8 @@
|
||||
</ItemsControl>
|
||||
|
||||
<Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}">
|
||||
<Grid ColumnDefinitions="30,50,90,40,45"
|
||||
<Grid ColumnDefinitions="50,50,90,40,45"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.LocalFile}}">
|
||||
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Item.Name, Converter={StaticResource GetFileExtensionConverter}}"/>
|
||||
@@ -52,6 +63,7 @@
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="4" Text="{Binding Item.Attributes}"/>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="90,40,45"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.Container}}">
|
||||
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Item.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
|
||||
@@ -59,6 +71,7 @@
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding Item.Attributes}"/>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="90,40,45"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.Element}}">
|
||||
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Item.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<Grid PointerPressed="HeaderPointerPressed">
|
||||
<Rectangle Fill="#01000000"/>
|
||||
<TextBlock Margin="15,10" Text="FileTime"/>
|
||||
<TextBlock Margin="15,10" Text="{Binding Title}"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" PointerPressed="HeaderPointerPressed">
|
||||
@@ -249,13 +249,12 @@
|
||||
IsTabStop="True"
|
||||
Items="{Binding AppState.SelectedTab.CurrentLocation.Items^}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
SelectedItem="{Binding AppState.SelectedTab.SelectedItem, Mode=TwoWay}"
|
||||
SelectionChanged="OnCurrentItemsSelectionChanged"
|
||||
Classes="ContentListView">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView/>
|
||||
<local:ItemView HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
@@ -154,11 +154,5 @@ namespace FileTime.Avalonia.Views
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCurrentItemsSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var listBox = this.FindControl<ListBox>("CurrentItems");
|
||||
listBox.ScrollIntoView(listBox.SelectedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user