maui icon indicating copy to clipboard operation
maui copied to clipboard

[Spec] Separate Text Wrapping and Overflow in Core

Open hartez opened this issue 3 years ago • 4 comments

The Problem

When dealing with text in an application, we have to answer a few questions:

  1. Is this text allowed to wrap to multiple lines?
  2. If so, how should that wrapping be done?
  3. If the text is longer than the allotted space, how should the overflow be handled?

Xamarin.Forms attempted to encompass the answers in 2 properties: LineBreakMode and MaxLines.

Unfortunately, this leads to problems:

  • MaxLines is only available on Label; other controls which might want to use multi-line text (e.g., a Button) have no way of limiting the number of lines of text
  • LineBreakMode conflates text overflow with line wrapping; because of this, situations such as multi-line word-wrapped text truncated at the start/end of the text are impossible on some platforms.

We can't avoid pulling the current behavior/members forward into Maui.Controls (because migration), but we can avoid pushing all these problems onto everyone else. And we can set ourselves up for better things in the future.

I'm proposing that for Maui.Core, we effectively split this functionality into separate enums and interfaces: TextOverflowMode, TextWrapMode, and IMultilineText.

API

Proposed Interfaces Changes for Core

interface IText 
{
	// ... adding this to the existing interface with a default implemenation
	TextOverFlowMode { get; }
}

interface IMultilineText : IText
{
	int MaximumLines { get; }
	TextWrapMode Wrap { get; }
}

enum TextWrapMode
{
	None, 		// Text does not wrap, regardless of number of lines allowed
	Word, 		// Text wraps at word boundaries
	Character 	// Text wraps at character boundaries (possibly mid-word)
}

enum TextOverflowMode
{
	Truncate,                  // Text is cut off
	EllipsizeEnd, 		// Text is cut off at the end with ...
	EllipsizeStart,		// Text is cut off at the start with ...
	EllipsizeMiddle,	// Text is spliced in the middle with ...
}

Other Interface Changes

ILabel : IMultilineText 	// Labels add support for multi-line text
IButton : IMultilineText 	// Buttons add support for multi-line text

There may be others (e.g., Editor) where this also makes sense.

Controls Implementation

All of these changes are in the Core layer, and will be remapped in Controls (at least for the first stable release) to preserve the old behavior.

Developer Experience

Forms migration should be seamless, as nothing is actually changing from Forms to Controls. Users who want the new experience can disable the remapping in Controls.

The new experience will provide options such as limited multiline text in Buttons, the ability to end ellipsize multiline text on Android (currently impossible), and a generally more flexible API.

Backward Compatibility

For Controls, nothing changes from Forms.

hartez avatar Apr 05 '22 15:04 hartez

@hartez I'm currently porting Fabulous from XF to Maui by providing my own implementation for all the interfaces (ILabel, IButton, etc) without relying on Maui.Controls.

(I must admit it is very hard to understand what's expected and when with close to no documentation... A lot of reverse engineering is required)

Unfortunately, https://github.com/dotnet/maui/pull/5936 removed the MaxLines / LineBreakMode properties from ILabel and gave no alternative except "we will do better in the future". This breaks layout in iOS because UILabel has Lines = 1 by default and won't auto-wrap.

This sample follows the Hello World template of dotnet new maui

Right now, it's a blocking issue for any 3rd party frameworks trying to plug into Maui. Something as basic as a Label isn't working correctly which make it a showstopper.

I seem to understand based on the spec here and some of your other messages on GitHub that this specification is set to be done "long term" / "in the future".

Given the blocking nature of this issue, could you please either:

  • Proritize this specification
  • Rollback #5936 until a better design is found

TimLariviere avatar Aug 20 '22 09:08 TimLariviere

For anyone interested, I managed to do a quick workaround that makes the Label always auto-wrap on iOS:

#if __IOS__

public class CustomLabelHandler: Microsoft.Maui.Handlers.LabelHandler
{
    protected override MauiLabel CreatePlatformView()
    {
        var label = base.CreatePlatformView();
        label.Lines = 0;
        return label;
    }
}
        
// Shadow Microsoft.Maui.Handlers.LabelHandler with CustomLabelHandler
using LabelHandler = CustomLabelHandler;

#else

using LabelHandler = Microsoft.Maui.Handlers.LabelHandler;

#endif

public class FabulousLabel: ILabel { ... }

var label = FabulousLabel();
label.Handler = LabelHandler();

TimLariviere avatar Aug 20 '22 10:08 TimLariviere

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

ghost avatar Aug 30 '22 15:08 ghost

Wouldn't it make sense to care about those who help building the ecosystem, and not randomly break their efforts and 'consider fixing this later' 🙄

ShalokShalom avatar Sep 17 '22 17:09 ShalokShalom

@TimLariviere I spent some time getting this implementation started - #10279.

Hopefully I'll be able to do some testing and finishing up next week. I'm not sure when we'll be able to get this released, though, because it does involve some API changes.

hartez avatar Sep 23 '22 03:09 hartez

Oh, also, there's another (admittedly poorly-documented) option that might help for this particular problem - the handlers for each control type allow you to specify an alternate factory method for the platform view without having to subclass the handler. It'd look something like this (in your startup configuration):

#if __IOS__
	Microsoft.Maui.Handlers.LabelHandler.PlatformViewFactory = (handler) => {
		return new UILabel { Lines = 0 };
	};
#endif

hartez avatar Sep 23 '22 03:09 hartez