Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use LineCanvas to draw TableView borders #2574

Draft
wants to merge 32 commits into
base: v2_develop
Choose a base branch
from

Conversation

Nutzzz
Copy link

@Nutzzz Nutzzz commented Apr 25, 2023

For my purposes, I wanted to add a way to save one or two lines in the table column header while still setting off the header, so I added Style.ShowHorizontalHeaderThroughline that is displayed if either or both of the overline and underline are disabled (see image below). It got pretty ugly, but since I also wanted to play with my LineCanvas style additions with TableView, I took it on myself to switch TableView to LineCanvas. This simplifies things quite a bit, but I'm not sure if I've gone about this the wrong way. I also added styles to apply different LineStyles to different portions of the table (header outside/inside and regular cell outside/inside). Anyway, I thought I'd share FWIW.

Also, By design, Style.ShowHorizontalScrollIndicators are now displayed on whatever is the lowest header line.

Issues:

  • Style.AlwaysUseNormalColorForVerticalCellLines can no longer be false [if cell lines are enabled]. I'm not sure at the moment how to fix that without an ugly hack.
  • I cleaned up some of the test failures that were related to Style.ShowHorizontalHeaderThroughline (and are thus by design), but there are a few remaining that I need to figure out.
  • It looks like there's currently an issue with the separator not being drawn when a cell is null. EDIT: I have a fix that works for TableEditor but for some reason not for FileDialog.

Pull Request checklist:

  • I've named my PR in the form of "Fixes #issue. Terse description."
  • My code follows the style guidelines of Terminal.Gui - if you use Visual Studio, hit CTRL-K-D to automatically reformat your files before committing.
  • My code follows the Terminal.Gui library design guidelines
  • I ran dotnet test before commit
  • I have made corresponding changes to the API documentation (using /// style comments)
  • My changes generate no new warnings
  • I have checked my code and corrected any poor grammar or misspellings
  • I conducted basic QA to assure all features are working

headerthruline

@Nutzzz
Copy link
Author

Nutzzz commented Apr 25, 2023

Should TableView.Border be responsible for the outer border?

@Nutzzz

This comment was marked as outdated.

@tig
Copy link
Collaborator

tig commented May 10, 2023

Should TableView.Border be responsible for the outer border?

This is a great question.

My initial reaction is yes. If tableView.Border has a + thickness and !LineStyle.None then any grid lines should draw -1 past the left and top bounds and +1 past the right and bottom bounds (using tableView.LineCanvas). This will make the border flow naturally and automatically.

However, there are edge cases
that will prove troublesome.

  • scroll bars
  • Border.Thickness.Top > 2

I need to think on this more. Would love your thoughts.

@Nutzzz
Copy link
Author

Nutzzz commented May 10, 2023

Would love your thoughts.

You hit the nail on the head. I had the same thoughts, and the same worries.

EDIT: Some additional concerns:

  1. I added styles in this PR to allow the header inner/outer borders to be different from the table body inner/outer borders. If the view Border is drawing the outer edge, this is no longer an option, or is at least another complication.
  2. Initially by accident--but now I like it, and question whether it ought to be by design--in this PR when ExpandLastColumn=false, the borders of the extra unused column aren't drawn. If the view Border is drawing the outer edge, this would revert to the current behavior.

@Nutzzz
Copy link
Author

Nutzzz commented Jan 20, 2024

@tig Yeah, doesn't look like that merge was sufficient. I'll update this to the latest v2_develop for what it might be worth... I'd like to play with some of the changes you guys have been doing in the meantime, in any case.

tig and others added 2 commits January 20, 2024 09:45
Also:
* Add BorderColor to TableStyle
* Move symbols to TableStyle
* Rename LineStyle to BorderStyle (at the risk of confusion with the View's border)
* Comment out AlwaysUseNormalColorForVerticalCellLines feature
@Nutzzz
Copy link
Author

Nutzzz commented Jan 21, 2024

OK, now we're back to where this PR was in May. I did finish the BorderColor style that I never got around to checking in (though the UICatalog option should switch to the new Color Picker).

However the remaining issues mentioned above haven't changed:

  1. I can work on getting the old behavior from ExpandLastColumn false (though I prefer it this way). EDIT: Fixed with a new option (default true)
  2. I'm not really sure how to fix AlwaysUseNormalColorForVerticalCellLines [though the behavior can be emulated by disabling ShowVerticalCellLines and setting a SeparatorSymbol].
  3. The bug with the missing vertical cell line after a DBNull cell when NullSymbol is string.Empty remains.

* TODO: Fix new unit tests
* Add new AddEmptyColumn (when ExpandLastColumn is false) added as a new option (default true)
* Add new InvertSelectedCell (the entire cell vs. existing option for first character only)
* Add new HeaderSeparatorSymbol distinct from SeparatorSymbol for regular cells
@Nutzzz
Copy link
Author

Nutzzz commented Jan 29, 2024

Finally fixed 1 from the list in the previous comment, but I'm still stuck on the other two. I added some new color unit tests, but I guess I'm not entirely sure how these work, and they're failing.

/// The symbol to add after each cell value and header value to visually seperate values (if not using vertical gridlines)
/// The symbol to add after each header value to visually seperate values (if not using vertical gridlines)
/// </summary>
public char HeaderSeparatorSymbol { get; set; } = ' ';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Per our guidelines and just because it's a plain ol' good idea, how about defining constants for these instead of the char literal?

Fast way to do that would be to highlight the ' ' in one spot, press ctrl-alt-c, to invoke the resharper introduce constant refactoring, choose the option that says "replace x instances", which will cover any it thinks are relevant within the same scope, and then pick a name for the constant such as SingleSpace (or anything else that would make sense and also not be a potential conflict with any future strings or other types with a similar value/purpose).

And of course not necessarily in this PR. Just making note of a place for us to dogfood our guidelines.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that if this is to be a constant it would make more sense as DefaultHeaderSeparatorSymbol rather than a global find and replace.

I think that approach would also work better with ConfigurationManager which has some precedent for changing defaults via settings files

Copy link
Collaborator

@dodexahedron dodexahedron left a comment

Choose a reason for hiding this comment

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

Good stuff.

The comments I added are just notes for future work and aren't necessary or in scope for this PR.

Terminal.Gui/Views/TableView/TableView.cs Outdated Show resolved Hide resolved
(!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) {
if (current.Width - colName.Length > 0
&& Style.ShowHorizontalHeaderThroughline
&& (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Micro-optimization opportunity (also not in scope for this PR):

By DeMorgan's theorem, (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline) is equivalent to !(Style.ShowHorizontalHeaderOverline && Style.ShowHorizontalHeaderUnderline)

Each short-circuits if Style.ShowHorizontalHeaderOverline is false, but an extra unary ! is avoided with the && form if it is true, as there's only one in the latter case.

This is probably also applied by the compiler for optimized builds, though.

But a thought about that:

In places where debug and release builds may have different compiled output, it's a good idea to try to make them be the same.

Copy link
Author

Choose a reason for hiding this comment

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

Haha, I don't need no stinkin' truth tables. I wasn't aware there was a difference in compiled output in this case. In fact, I guess I sort of always assumed that if there was, that a disjunction (with the potential for a "short-circuit") would be better, since (potentially) having to look up the additional value in memory would be much more expensive than doing the negation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Haha well as stated it is a micro-optimization and, in cases like that, my opinion is that clarity of intent is much more important than shaving off one operator method call in a subset of cases.

In this particular instance, they are identical except for the single situation I called out, so it's truly a super-micro-optimization.

But, for future reference, a lot of commentary I make, unless I put it in an actual change request review, are purely for visibility of opportunities, concepts, etc for discussion or for consideration for future work.

If I see an actual problem that I think should be addressed before merge, I'll be pretty clear about it and request a change in the review.

The only thing about the code in this PR that came close to that for me is that we strongly prefer switches (either expressions or statements as appropriate) for control flow logic, when possible, both for style and performance reasons.

But that's IMO not important enough, at this stage, to block a PR like this one. 😊

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh and thanks for working on this.

Do you need any assistance, with regard to #2574 (comment) or anything else for this?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks. Any assistance would be appreciated, and as I won't have time to work on this for the next few days there shouldn't be any duplication of effort.

Copy link
Author

@Nutzzz Nutzzz Feb 1, 2024

Choose a reason for hiding this comment

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

Done, but only by dint of allowing users to make the strange choice of using all three lines.

Also, regarding switch-case, I don't use them unless there are at least 3 options [I assume that's standard?]; here there are a few 2-option if-else's and 2-option if-else-if's without a default else.

Of course that's not to say that some cleanup couldn't be done; this still retains much of the way the original version was structured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants