kirill-grouchnikov / radiance

Building modern, elegant and fast Swing applications
BSD 3-Clause "New" or "Revised" License
794 stars 89 forks source link

Extending RadianceTableUI #394

Closed chipu closed 2 years ago

chipu commented 2 years ago

Version of Radiance (latest development is 6.0-SNAPSHOT)

Sub-project (Common, Animation, Theming, Component, ...)

Version of Java (current minimum is 9)

Version of OS

The issue you're experiencing (expected vs actual, screenshot, stack trace etc)

chipu commented 2 years ago

Hi, I am sorry if this question is answered somewhere... I could not find it. I used substance 6.7, and it want to migrate to Java11, so I decided to migrate to radiance 1.0, then to java11 with radiance 2.0, and then to radiance 5.0. But I was extending SubstanceTableUI (I have a feature to be able to expand a row, and showing a nested table), but now it has a private constructor, so I cannot extend it any more... Any suggestions here? It is possible to fully customize painting a specific row of the table? (if it is possible to do it in 5.0, then i can try to move directly to 5.0 before moving the code to production) Thanks

chipu commented 2 years ago

I am also using extension to implement a 3-state radiobutton based on SubstanceRadioButtonUI... and I got the same issue here as well

kirill-grouchnikov commented 2 years ago

I'll need to update the FAQ (see the "Can I create my own look-and-feel on top of Radiance?" question in there), as indeed the UI delegates are now private, very much intentionally.

I strongly consider everything in the UI delegates to be internal implementation details with no guarantees on the stability or compatibility between Radiance delegates and the core Swing base UI delegates, as well as between those Radiance delegates and the application code.

The intent is to have application code use Radiance painters and animation APIs to create custom visuals that are based on the current skin and follow the needs of that particular application.

With this in mind, I can either point you to the code of those UI delegates and how they use these APIs (with some internal utilities for tracking transition animations, which you may or may not need), or ask to see how your custom visuals for the table rows and tri-state radio buttons look like so I can advise on the right painter APIs to use.

kirill-grouchnikov commented 2 years ago

Also, the minimum requirement in Radiance is Java 9, not 11

chipu commented 2 years ago

Another option would be copying the code for those UI delegates, and using them outside radiance class hierarchy... would that play well with the rest of the L&F (just losing the animations would be acceptable)? I will paste screenshot of the current features, and I would be grateful if you could point me in directions to implement them: Requirement is Java 9, yes, but I prefer to use a LTS version :)

table with expanded row: image

3-state checkbox. The look for the indeterminate state is not really important (I just reused the one from radiobutton). Here I override getDefaultIcon() to specify the third icon, if the state is the third one image

"flat" combobox. Low priority, as this is just "look". This is just a combobox without difference between text background and arrow background. I guess this should be easy to use with the painters (I will look into that api later) image

in other place (I dont have the screenshot in this case), I extend SubstanceComboBoxUI to override ComboPopup createPopup(), to show a list of items as a tree (with some indentation before each item)

Just for completeness, because right now it works fine: I also override SubstancePopupPanelUI (but here the constructor is not private), to show a panel after clicking on a button (jcommandbutton if I remember correctly), and allow using secondary click on the panel without the panel been closed image

PD: Thanks for this fantastic library, it looks great and has nice features!

kirill-grouchnikov commented 2 years ago

Code snippet starting here is an example of how to display Radiance popup content. This line is using a public API to configure the popup to not be dismissed when the user interacts with the content inside that popup.

A question about the expanded row in the table. Which specific row is that? What in it cannot be achieved by custom cell renderers for the content in that row?

chipu commented 2 years ago

Thanks, I will review those examples tomorrow. about the expanded row, this screenshot shows one normal row (blue background) and one expanded row (everything else, a nested table with some radio buttons on top): image the expanded row does not respect the columns in the table, but it is drawn as the last column in the table, filing the space of the full table

Do you have any hint about how to choose the icon to show in a checkbox (to be able to provide in some cases a third icon)?

Thanks for your help!

PD: for the record, this is the old implementation of the ExpandableTableUI that extends SubstanceTableUI:

private static final class ExpandableTableUISubstance extends SubstanceTableUI { private int lastColumn; @Override protected void paintCell(Graphics g, Rectangle cellRect, Rectangle highlightCellRect, int row, int column) { if (((ResourceTableModel<?>) table.getModel()).isExpandedRow(row)) { if (column <= lastColumn) { Color c = g.getColor(); g.setColor(table.getGridColor()); cellRect.x = EXPANDED_LEFT_INSET; cellRect.width = table.getWidth() - EXPANDED_LEFT_INSET; g.drawRect(cellRect.x, cellRect.y, cellRect.width, cellRect.height);

            int spacingHeight = table.getRowMargin();
            int spacingWidth = 0;//table.getColumnModel().getColumnMargin();
            cellRect.setBounds(cellRect.x + spacingWidth, cellRect.y + spacingHeight, cellRect.width - spacingWidth, cellRect.height - spacingHeight);
            g.fillRect(0, cellRect.y, EXPANDED_LEFT_INSET / 2, cellRect.height);
            g.drawRect(EXPANDED_LEFT_INSET / 2, cellRect.y, EXPANDED_LEFT_INSET / 2 - 1, cellRect.height);
            cellRect.x = EXPANDED_LEFT_INSET;

            g.setColor(c);

            Component component = table.prepareRenderer(FieldTable.DEFAULT_RENDERER, row, column);
            if (component.getParent() == null)
                rendererPane.add(component);
            rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
        }
        lastColumn = column;
    } else
        super.paintCell(g, cellRect, highlightCellRect, row, column);
}

}

kirill-grouchnikov commented 2 years ago

Which one is the expanded row? Is it the one with all the radio buttons, sort of a table-wide "merged cell"?

I'll need to take a look at the tri-state checkbox to see what is the optimal way to implement it.

chipu commented 2 years ago

yes, that one it is I appreciate that

kirill-grouchnikov commented 2 years ago

Do you override paintCells and look at the row index? And then do some sort of a custom logic to call myFullRowRenderer.paintComponent on that whole row? If so, how do you handle clicks on each one of the radio buttons?

JTable doesn't really support merged cells out of the box, but there's some code online like here that shows how to use a custom table model along with extending JTable and overriding its public getCellRect - that way the UI delegate, Radiance or not, would be calling the same methods as it has now. Having said that, they also use a custom table UI delegate that is aware of these spanned / merged cells, so that would still need access to RadianceTableUI.

jkdoug commented 2 years ago

I'm also attempting to implement a tristate check box (for use in a custom tree control), and I'm very interested to hear any and all advice on this thread! Thank you for sharing your code, insights, and advice!

chipu commented 2 years ago

yes, I override paintCells() and have that custom logic there. about the clicks, I do nothing special, when rendering, I render a JPanel, with radiobuttons, and i add a mouselistener to those radiobuttons (I think, but not sure, that I had issues when the horizontal scrollbar was not at value 0)... Thanks, I will check that code too :)

PD:If you think this expanded table could be interesting to you, I can try convincing my company to let me contribute that code to radiance...

kirill-grouchnikov commented 2 years ago

Specifically for the tri-state checkbox and how apps can "extend" the existing look-and-feel visuals into that third state, I don't think there's a good answer across currently active look-and-feels.

Apart from the interaction model itself (what do consecutive clicks do, accessibility aspects, etc), I'm focusing more on the layout of different elements, the visuals of the checkbox / mark and the animations.

The layout has paddings, margins and gaps around the checkmark and the text, and that is "usually" controlled by various entries in the UIManager default table - which I consider to be a rather awkward API of a sorts that didn't really age well. Some LAFs such as FlatLaf go all in on that approach, adding their own custom entries for controlling the layout metrics.

Then there's the checkbox / checkmark as well:

image

You have:

Different LAFs choose to expose some of these to be configurable, but pretty much all of them do not provide control over the path of the mark itself:

There is no API surface as far as I can see that would allow application code to "query" how exactly to draw a custom tri-state checkbox mark in the indefinite state so that it follows not just the styling of the two-state checkbox, but also all the inner layout metrics. This is probably why some of these libraries also provide explicit support for a tri-state checkbox.

Committing to such a lower-level API in general is a double edged sword. On one hand, it will allow application code a much better way to emulate the smaller details of how core components are rendered. On the other hand, it does lock the look-and-feel itself into either A) having to support that API forever and not being able to meaningfully evolve its visual language or B) having to break that API every time a visual change is made (or the underlying implementation details do)

So pretty much, as far as I can see, application code that does wish to create a tri-state checkbox needs to copy-paste a lot of these implementation details, even if the UI delegate and the icon class are part of the look-and-feel public API surface.

With all this in mind, I am considering adding a tri-state checkbox component to Radiance in the next release.

kirill-grouchnikov commented 2 years ago

If you think this expanded table could be interesting to you, I can try convincing my company to let me contribute that code to radiance

I don't really see myself starting down the path of providing an extended data table / grid. There's just way too many interlocking features that a fully fledged component like that would need to support - grouping, filtering, single and multi-column filtering, pivots, multirow headers, footers, search, freezing rows and columns, pinning rows during scrolling, column / row / cell selection, nesting, cell spans - just off the top of my head.

kirill-grouchnikov commented 2 years ago

With all this in mind, I am open to marking the constructors for UI delegates of core Swing components as protected, but leaving them in the internal package with no guarantees on compatibility.

chipu commented 2 years ago

marking the constructors as protected would work great for me. I have no problem about updating my small customizations whenever I upgrade the library, if anything has changed

I am very happy to hear that tri-state checkbox could be included :)

Thank you very much