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

feat: Picker - initial scroll position #1942

Merged
merged 30 commits into from
Apr 24, 2024

Conversation

bmingles
Copy link
Contributor

@bmingles bmingles commented Apr 17, 2024

NOTE: This PR depends on deephaven/deephaven-plugins#424 in order for element type checks to work for Item, Text, Section, etc.

  • Refactored non-jsapi Picker to support JSX children with minimal wrapping instead of having to normalize items (Picker + ListView - icon_column #1890 should do the same for ListView, and we should be able to delete some of the normalization code)
  • Updated scroll position logic to be able to traverse JSX elements
  • Disable initial scrolling when children contain descriptions or sections

Testing
This illustrates different configurations and shows how initial scroll behavior differs for Pickers with plain items, descriptions, or sections:

import deephaven.ui as ui
from math import floor
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count=1000
items_table = time_table("PT1S", start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count)).update([
    "Id=new Integer(i)",
    "Display=new String(`Display `+i)",
])

@ui.component
def pickers():
    value, set_value = ui.use_state('2SSS')

    def handle_change(v):
        print(v)
        set_value(v)

    on_change = ui.use_callback(handle_change, [])

    # Picker with text options
    text = ui.picker(
        label="Text",
        children=[
            'Text 1',
            'Text 2',
            'Text 3'
        ]
    )

    # Picker with boolean options
    boolean = ui.picker(
        label="Boolean",
        children=[
            True,
            False
        ]
    )

    # Picker with numeric options
    numeric = ui.picker(
        label="Numeric",
        children=[
            10,
            20,
            30
        ]
    )

    ################ Icons #######################################

    # Icons
    icons = ui.picker(
        label = "Icons",
        children = [
            item_with_icon('Add', 'vsAdd'),
            item_with_icon('Remove', 'vsRemove'),
        ]
    )

    # Icons (default selection)
    icons_default_selection = ui.picker(
        label = "Icons (default selection)",
        default_selected_key="3GGG",
        children = list(map(
            lambda args : item(args[1]) if args[0] % 7 > 0 else item_with_icon(args[1], 'vsAccount'), 
            enumerate(generate_item_texts(0, 500))))
    )

     # Icons (controlled)
    icons_controlled = ui.picker(
        label = "Icons (controlled)",
        selected_key=value,
        on_change=on_change,
        children = list(map(
            lambda args : item(args[1]) if args[0] % 7 > 0 else item_with_icon(args[1], 'vsAccount'), 
            enumerate(generate_item_texts(0, 500))))
    )

    ################ Descriptions #######################################

    # Descriptions (default selection)
    descriptions = ui.picker(
        label = "Descriptions",
        children = list(map(lambda txt : item(txt, True), generate_item_texts(0, 500)))
    )

    # Descriptions (default selection)
    descriptions_default_selection = ui.picker(
        label = "Descriptions (default selection)",
        default_selected_key="3GGG",
        children = list(map(lambda txt : item(txt, True), generate_item_texts(0, 500)))
    )

    # Descriptions (controlled)
    descriptions_controlled = ui.picker(
        label = "Descriptions (controlled)",
        selected_key=value,
        on_change=on_change,
        children = list(map(lambda txt : item(txt, True), generate_item_texts(0, 500)))
    )

    ################ Sections #######################################

    # Sections
    sections = ui.picker(
        label = "Sections (default selection)",
        children = [
            section(x, i * 10, i * 10 + 9) for i, x in enumerate(generate_item_texts(0, 19))
        ]
    )

    # Sections (default selection)
    sections_default_selection = ui.picker(
        label = "Sections (default selection)",
        default_selected_key = "3GGG",
        children = [
            section(x, i * 10, i * 10 + 9) for i, x in enumerate(generate_item_texts(0, 19))
        ]
    )

    # Sections (controlled)
    sections_controlled = ui.picker(
        label = "Sections (controlled)",
        selected_key=value,
        on_change=on_change,
        children = [
            section(x, i * 10, i * 10 + 9) for i, x in enumerate(generate_item_texts(0, 19))
        ]
    )

    ################ Tables #######################################

    table_value, set_table_value = ui.use_state('Display 824')

    # Uncontrolled table with default selection
    table = ui.picker(
        items_table,
        key_column="Display",
        label_column="Display",
        label="Table",
    )

    # Uncontrolled table with default selection
    table_default_selection = ui.picker(
        items_table,
        key_column="Display",
        label_column="Display",
        label="Table (default selection)",
        default_selected_key="Display 86",
    )

    # Controlled table
    table_controlled = ui.picker(
        items_table,
        key_column="Display",
        label_column="Display",
        label="Table (controlled)",
        on_selection_change=set_table_value,
        selected_key=table_value,
    )

    return ui.flex(
        direction="column",
        UNSAFE_style={"overflow":"scroll"},
        children = [
            ui.heading("Basic", margin=0),
            ui.flex(
                direction='row',
                children=[
                    text,
                    boolean,
                    numeric,
                ]
            ),
            ui.heading("Icons", margin=0),
            ui.flex(
                direction='row',
                children=[
                    icons,
                    icons_default_selection,
                    icons_controlled,
                ]
            ),
            ui.heading("Descriptions", margin=0),
            ui.flex(
                direction='row',
                children=[
                    descriptions,
                    descriptions_default_selection,
                    descriptions_controlled,
                ]
            ),
            ui.heading("Sections", margin=0),
            ui.flex(
                direction='row',
                children=[
                    sections,
                    sections_default_selection,
                    sections_controlled,
                ]
            ),
            ui.heading("Table", margin=0),
            ui.flex(
                direction='row',
                children=[
                    table,
                    table_default_selection,
                    table_controlled,
                ]
            )
        ],
    )

pick = pickers()

################ Helpers #######################################

# Generate a list of unique item text for a start / stop range
def generate_item_texts(start, stop):
    characters = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    size = len(characters)
    return [str(floor((i + start) / size)) + (characters[x % size] * 3) for i, x in enumerate(range(start, stop))]

@ui.component
def item(txt, include_description=False):
    return ui.item(
        ui.text(txt, key="label"),
        ui.text("Description " + txt, key="description", slot="description"),
        # key=txt,
        text_value=txt
    ) if include_description else ui.item(txt, text_value=txt)

@ui.component
def item_with_icon(txt, icon):
    return ui.item(
        text_value=txt,
        children=[
            ui.icon(icon),
            txt,
        ]
    )

@ui.component
def section(txt, start, end):
    return ui.section(
        title = "Section " + txt,
        children = list(map(lambda txt : item(txt), generate_item_texts(start, end)))
    )

resolves #1935

@bmingles bmingles force-pushed the 1935-picker-initial-scroll branch from 36fe15d to 703268b Compare April 19, 2024 15:10
@bmingles bmingles requested a review from mofojed April 22, 2024 19:40
@bmingles bmingles marked this pull request as ready for review April 22, 2024 19:40
Copy link
Member

@mofojed mofojed left a comment

Choose a reason for hiding this comment

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

Some minor cleanup, otherwise looks good.

@bmingles bmingles requested a review from mofojed April 24, 2024 14:57
@bmingles bmingles force-pushed the 1935-picker-initial-scroll branch from 8ae77ea to 1f37fde Compare April 24, 2024 22:25
@bmingles bmingles merged commit 5f49761 into deephaven:main Apr 24, 2024
4 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Apr 24, 2024
@bmingles bmingles deleted the 1935-picker-initial-scroll branch April 25, 2024 20:56
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Picker - determine initial scroll position for variable height items
2 participants