Skip to content

Conversation

@Rexios80
Copy link
Member

Right now, copying text in a SelectionArea copies it with no separation. Since SelectionArea is very useful with vertical lists of Text widgets this is not ideal. This PR adds a separator field to SelectionArea so that developers can add separators such as newlines between selected Text widgets.

#104548

I added a new test for this

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels Feb 21, 2023
@Rexios80 Rexios80 changed the title Add separator field to SelectionArea Add separator field to SelectionArea and allow composition of SelectableRegions Feb 21, 2023
@Rexios80
Copy link
Member Author

It wasn't sitting well with me that nested SelectableRegions didn't coordinate selection with parent SelectableRegions.

With composition it is now possible to have the following widget tree

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: SelectionArea(
          separator: '\n',
          child: Column(
            children: <Widget>[
              Text('How are you?'),
              SelectionArea(
                separator: ' ',
                child: Row(
                  children: <Widget>[
                    Text('Good, and you?'),
                    Text('Fine, thank you.'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

copy/paste this

How are you?
Good, and you? Fine, thank you.

@HansMuller HansMuller requested a review from chunhtai February 22, 2023 01:21
Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API will hardcode the behavior for entire selection area which is not something we want to do. I think the separator should be added into MultiSelectableSelectionContainerDelegate and then each multichild widget such as Row or column should wrap its own selection container and set the separate accordingly.

Another thing to consider is whether we should convert to copy html text instead, some desktop platforms can store html into clipboard, but i have not tested them all.

@Rexios80
Copy link
Member Author

This behavior should definitely be developer configurable. I don't think we should do anything web-specific here such as copying html. I'll look into doing something with rows/columns.

@Rexios80
Copy link
Member Author

Rexios80 commented Feb 22, 2023

Actually, I think these changes already handle that issue. You can now nest SelectionAreas to override the parent's separator.

I'm not sure touching anything else makes sense.

@Rexios80
Copy link
Member Author

Rexios80 commented Feb 22, 2023

If we did go the route of MultiChild widgets handling the separation, we would probably need to create SelectableFlex, SelectabelRow, SelectableColumn, etc in which case SelectionArea would have no reason to exist.

@chunhtai
Copy link
Contributor

This behavior should definitely be developer configurable. I don't think we should do anything web-specific here such as copying html. I'll look into doing something with rows/columns.

I have not looked into this in more detail but I think you can store html into clipboard in macos https://developer.apple.com/documentation/appkit/nspasteboardtypehtml?language=objc

This will be useful to store rich text and be able to reuse in other application that can handle pasting html

@chunhtai
Copy link
Contributor

If we did go the route of MultiChild widgets handling the separation, we would probably need to create SelectableFlex, SelectabelRow, SelectableColumn, etc in which case SelectionArea would have no reason to exist.

No, The Flex can wrap the subtree with a selection container similar to how scrollable does that. It conditionally wrap the selection container only if there is another selection container above.

@chunhtai
Copy link
Contributor

Actually, I think these changes already handle that issue. You can now nest SelectionAreas to override the parent's separator.

I'm not sure touching anything else makes sense.

I think you meant SelectionContainer, I don't think we have good support for nesting SelectionArea.

@Rexios80
Copy link
Member Author

I made nesting SelectionAreas work in this PR, but I like what you're saying about making Flex handle this. I'll work on implementing that.

@Rexios80
Copy link
Member Author

Rexios80 commented Feb 22, 2023

An issue I'm seeing with that approach is Scrollable is a StatefulWidget whereas Flex is a MultiChildRenderObjectWidget which doesn't have a build method to do the SelectionContainer logic in

@Rexios80
Copy link
Member Author

What exactly is the issue if this code works?

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: SelectionArea(
          separator: '\n',
          child: Column(
            children: <Widget>[
              Text('How are you?'),
              SelectionArea(
                separator: ' ',
                child: Row(
                  children: <Widget>[
                    Text('Good, and you?'),
                    Text('Fine, thank you.'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

@chunhtai
Copy link
Contributor

An issue I'm seeing with that approach is Scrollable is a StatefulWidget whereas Flex is a MultiChildRenderObjectWidget which doesn't have a build method to do the SelectionContainer logic in

You can make column and row a statelessWidget and in the build method wrapps SelectionContainer.

What exactly is the issue if this code works?

there are two issues:

  1. SelectionArea is basically a selectionContainer with gesture and selectionoverlay logic. but in this case, you don't need the later two, so the nested SelectionArea should be replaced with SelectionContainer

  2. We don't expect developer to need to manually insert these nested SelectionArea themselves, for obvious cases like Column,Row, or vertical scrollable, it should hardcode the separator directly.

@Rexios80
Copy link
Member Author

Thank you for explaining. This is my first PR on the flutter framework itself so I'm very new to understanding how all these pieces fit together. I wasn't sure if changing the inheritance of Row, Column, etc would be considered a breaking change.

@chunhtai
Copy link
Contributor

I don't think so, as long as the customer tests do not fail, we don't consider it a breaking change. Also I don't think we promise the type of a widget to stay the same as long as it is a still a Widget

Rexios80 added a commit to Rexios80/packages_flutter that referenced this pull request Apr 12, 2023
@Rexios80
Copy link
Member Author

@Rexios80
Copy link
Member Author

Although it looks like Google testing is passing now???

@chunhtai chunhtai self-requested a review April 12, 2023 17:23
@Hixie
Copy link
Contributor

Hixie commented Apr 12, 2023

Have we run benchmarks on this? Making Row and Column into stateful widgets seems like a really expensive change that will have huge impact across the board on Flutter apps. I would expect this to increase build times and increase memory usage. These are really basic widgets used all over the place.

@Rexios80
Copy link
Member Author

@Hixie Yeah I wasn't too sure about making Row and Column more complicated either. That's why the original changes had the separator handling on the SelectionArea itself.

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should directly make Column into StatefulWidget, You can create a wrapper widget maybe call _SelectedTextSeparator, which is a statefulwidget. the Column should remain a stateless widget and only wrap the _FlexBase with _SelectedTextSeparator if there is a selectionContainer

@Hixie
Copy link
Contributor

Hixie commented Apr 12, 2023

I'm very skeptical about any changes to Row or Column at all here. These are really core widgets that should not need to know about text selection at all, IMHO. They're just ways to configure the RenderFlex render object.

@chunhtai
Copy link
Contributor

chunhtai commented Apr 12, 2023

I agree with column should be simple, but that means people would have to do something extra to make column work correctly when selected. think people would likely ignored it and make app with bad user experience.

Another way to solve this is to make the selection container smart enough to know whether to add a \n or not by comparing the rect for each selectable texts. for example if the y axis does not overlap, we add \n, if the x axis has enough gap we add \t.
This will be very opinionated and hard to customize though.

@Hixie
Copy link
Contributor

Hixie commented Apr 12, 2023

That might be more reliable than having to teach every render object (or widget that wraps a render object) how to handle selection though. For example, how would we make it work for Stack? Surely that would have the same problem as Flex.

@Rexios80
Copy link
Member Author

Should we just go back to what I had before where the SelectionArea handled it?

@chunhtai
Copy link
Contributor

chunhtai commented Apr 12, 2023

@Rexios80

I assume you meant adding an API in MultiSelectableSelectionContainerDelegate to customize how separator are inserted?

I think this is what we can do, in MultiSelectableSelectionContainerDelegate.getSelectedContent when we concatenate SelectedContent, we should compare the coordinate of each selectable and add '\n' or '\t'. I think this should be enough for fixing most of the use cases.

If people really want to change the separator their way, we can expose an API in MultiSelectableSelectionContainerDelegate that takes a list of Selectables and return a SelectedContent. This API will be called in getSelectedContent to get the result.

@Rexios80
Copy link
Member Author

Comparing coordinates seems fragile and like it might have a bunch of edge cases. What I meant is just doing what the original PR did which let you do this:

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: SelectionArea(
          separator: '\n',
          child: Column(
            children: <Widget>[
              Text('How are you?'),
              SelectionArea(
                separator: ' ',
                child: Row(
                  children: <Widget>[
                    Text('Good, and you?'),
                    Text('Fine, thank you.'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

@chunhtai
Copy link
Contributor

This proposal would require people to manually wraps the widget. I think we should at least have some solution that would automatically do that for most of the case. Based on the discussion so far it seems comparing the rect seems to be a more reliable solution

@Rexios80
Copy link
Member Author

Users already have to wrap the widgets in SelectionArea for them to be selectable, so it's not a huge ask to just have them provide a separator if they want one. Adding separators automatically would be ideal, but currently there is no way to add separators at all and SelectionArea is mostly useless. Also if the field is on SelectionArea it is very easy for users to configure it.

@Rexios80
Copy link
Member Author

Do we even have access to the information we need to make the separator logic automatic in SelectableRegion? All we have access to is the Selectables which only have a way to read the selected content and nothing about the widget containing it.

@chunhtai
Copy link
Contributor

@dsyrstad
Copy link

@Rexios80 @chunhtai @Hixie In the interests of doing the simplest thing possible to address #104548. I think we should implement @Rexios80 's very first commit fff1cae. This implements the separator, is minimally invasive, is backwards compatible, and we could get it in now. Doing something more sophisticated obviously requires more thought and is more involved. A more sophisticated solution could also be in the API when the time comes.

For us, it's really important to fix #104548, otherwise copying a region of text and pasting it somewhere is nearly useless because the text is jammed together.

@Rexios80
Copy link
Member Author

@dsyrstad
Copy link

@dsyrstad What about everything up to bece955?

That would be these changes: https://github.com/flutter/flutter/pull/121112/files/bece9551b235253d69d4c35875706d95a41f954d

@Rexios80 I'm not sure what the registrar change does and how it relates to implementing a separator. Can you explain?

@Rexios80
Copy link
Member Author

That makes nested SelectionArea widgets work so you can have different separators for different portions of the text

@dsyrstad
Copy link

That makes nested SelectionArea widgets work so you can have different separators for different portions of the text

@Rexios80 Great, that looks good to me!

@Rexios80
Copy link
Member Author

Closing this PR in favor of #126835

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants