From 1fec1437e2235bd3a251082a1637840b2d0e9db2 Mon Sep 17 00:00:00 2001 From: Harkiran Bolaria Date: Mon, 2 Oct 2023 13:12:49 +0000 Subject: [PATCH] Basic widget for text paragraphs, used in scroll jank plugin. Change-Id: I6674820bedacc5289b8c045ad089331b6e5c9758 --- ui/src/assets/perfetto.scss | 1 + ui/src/assets/widgets/text_paragraph.scss | 22 +++++++ ui/src/frontend/widgets_page.ts | 45 ++++++++++++++ .../event_latency_details_panel.ts | 39 +++++++------ .../scroll_details_panel.ts | 37 ++++++------ .../scroll_jank_v3_details_panel.ts | 19 +++--- ui/src/widgets/text_paragraph.ts | 58 +++++++++++++++++++ 7 files changed, 178 insertions(+), 43 deletions(-) create mode 100644 ui/src/assets/widgets/text_paragraph.scss create mode 100644 ui/src/widgets/text_paragraph.ts diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss index 750c7882c2..28c2fb357e 100644 --- a/ui/src/assets/perfetto.scss +++ b/ui/src/assets/perfetto.scss @@ -50,3 +50,4 @@ @import "widgets/editor"; @import "widgets/vega_view"; @import "widgets/hotkey"; +@import "widgets/text_paragraph"; diff --git a/ui/src/assets/widgets/text_paragraph.scss b/ui/src/assets/widgets/text_paragraph.scss new file mode 100644 index 0000000000..b9b2d7d1f8 --- /dev/null +++ b/ui/src/assets/widgets/text_paragraph.scss @@ -0,0 +1,22 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@import "theme"; + +.pf-text-paragraph { + white-space:pre-wrap; + padding-top:5px; + padding-bottom:5px; + word-break:break-word; +} \ No newline at end of file diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts index fe239187f7..c3b6787956 100644 --- a/ui/src/frontend/widgets_page.ts +++ b/ui/src/frontend/widgets_page.ts @@ -39,6 +39,7 @@ import {FilterableSelect, Select} from '../widgets/select'; import {Spinner} from '../widgets/spinner'; import {Switch} from '../widgets/switch'; import {TextInput} from '../widgets/text_input'; +import {MultiParagraphText, TextParagraph} from '../widgets/text_paragraph'; import {LazyTreeNode, Tree, TreeNode} from '../widgets/tree'; import {createPage} from './pages'; @@ -969,6 +970,50 @@ export const WidgetsPage = createPage({ platform: new EnumOption('auto', ['auto', 'Mac', 'PC']), }, }), + m( + WidgetShowcase, { + label: 'Text Paragraph', + description: `A basic formatted text paragraph with wrapping. If + it is desirable to preserve the original text format/line breaks, + set the compressSpace attribute to false.`, + renderWidget: (opts) => { + return m(TextParagraph, { + text: `Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Nulla rhoncus tempor neque, sed malesuada eros + dapibus vel. Aliquam in ligula vitae tortor porttitor + laoreet iaculis finibus est.`, + compressSpace: opts.compressSpace, + }); + }, + initialOpts: { + compressSpace: true, + }, + }), + m( + WidgetShowcase, { + label: 'Multi Paragraph Text', + description: `A wrapper for multiple paragraph widgets.`, + renderWidget: () => { + return m(MultiParagraphText, + m(TextParagraph, { + text: `Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Nulla rhoncus tempor neque, sed malesuada eros + dapibus vel. Aliquam in ligula vitae tortor porttitor + laoreet iaculis finibus est.`, + compressSpace: true, + }), m(TextParagraph, { + text: `Sed ut perspiciatis unde omnis iste natus error sit + voluptatem accusantium doloremque laudantium, totam rem + aperiam, eaque ipsa quae ab illo inventore veritatis et + quasi architecto beatae vitae dicta sunt explicabo. + Nemo enim ipsam voluptatem quia voluptas sit aspernatur + aut odit aut fugit, sed quia consequuntur magni dolores + eos qui ratione voluptatem sequi nesciunt.`, + compressSpace: true, + }), + ); + }, + }), ); }, }); diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts index da726723db..4770ebe8bd 100644 --- a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts +++ b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts @@ -32,6 +32,7 @@ import {asSliceSqlId} from '../../frontend/sql_types'; import {DetailsShell} from '../../widgets/details_shell'; import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout'; import {Section} from '../../widgets/section'; +import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph'; import {Tree, TreeNode} from '../../widgets/tree'; import { @@ -134,23 +135,27 @@ export class EventLatencySliceDetailsPanel extends private getDescriptionText(): m.Child { return m( - `div[style='white-space:pre-wrap']`, - `EventLatency tracks the latency of handling a given input event - (Scrolls, Touchs, Taps, etc). Ideally from when the input was read by - the hardware to when it was reflected on the screen.{new_lines} - - Note however the concept of coalescing or terminating early. This occurs - when we receive multiple events or handle them quickly by converting - the into a different event. Such as a TOUCH_MOVE being converted into a - GESTURE_SCROLL_UPDATE type, or a multiple GESTURE_SCROLL_UPDATE events - being formed into a single frame at the end of the - RendererCompositorQueuingDelay.{new_lines} - - *Important:* On some platforms (MacOS) we do not get feedback on when - something is presented on the screen so the timings are only accurate - for what we know on a given platform.`.replace(/\s\s+/g, ' ') - .replace(/{new_lines}/g, '\n\n') - .replace(/ Note:/g, 'Note:'), + MultiParagraphText, + m(TextParagraph, { + text: `EventLatency tracks the latency of handling a given input event + (Scrolls, Touches, Taps, etc). Ideally from when the input was + read by the hardware to when it was reflected on the screen.`, + }), + m(TextParagraph, { + text: + `Note however the concept of coalescing or terminating early. This + occurs when we receive multiple events or handle them quickly by + converting them into a different event. Such as a TOUCH_MOVE + being converted into a GESTURE_SCROLL_UPDATE type, or a multiple + GESTURE_SCROLL_UPDATE events being formed into a single frame at + the end of the RendererCompositorQueuingDelay.`, + }), + m(TextParagraph, { + text: + `*Important:* On some platforms (MacOS) we do not get feedback on + when something is presented on the screen so the timings are only + accurate for what we know on a given platform.`, + }), ); } diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts index 19f698d679..77c6ceea77 100644 --- a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts +++ b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts @@ -39,6 +39,7 @@ import {DurationWidget} from '../../widgets/duration'; import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout'; import {Section} from '../../widgets/section'; import {SqlRef} from '../../widgets/sql_ref'; +import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph'; import {dictToTreeNodes, Tree} from '../../widgets/tree'; import { @@ -288,22 +289,24 @@ export class ScrollDetailsPanel extends private getDescriptionText(): m.Child { return m( - `div[style='white-space:pre-wrap']`, - `The interval during which the user has started a scroll ending after - their finger leaves the screen and any resulting fling animations have - finished.{new_lines} - - Note: This can contain periods of time where the finger is down and not - moving and no active scrolling is occurring.{new_lines} - - Note: Sometimes if a user touches the screen quickly after letting go - or Chrome was hung and got into a bad state. A new scroll will start - which will result in a slightly overlapping scroll. This can occur due - to the last scroll still outputting frames (to get caught up) and the - "new" scroll having started producing frames after the user has started - scrolling again.`.replace(/\s\s+/g, ' ') - .replace(/{new_lines}/g, '\n\n') - .replace(/ Note:/g, 'Note:'), + MultiParagraphText, + m(TextParagraph, { + text: `The interval during which the user has started a scroll ending + after their finger leaves the screen and any resulting fling + animations have finished.`, + }), + m(TextParagraph, { + text: `Note: This can contain periods of time where the finger is down + and not moving and no active scrolling is occurring.`, + }), + m(TextParagraph, { + text: `Note: Sometimes if a user touches the screen quickly after + letting go or Chrome was hung and got into a bad state. A new + scroll will start which will result in a slightly overlapping + scroll. This can occur due to the last scroll still outputting + frames (to get caught up) and the "new" scroll having started + producing frames after the user has started scrolling again.`, + }), ); } @@ -344,7 +347,7 @@ export class ScrollDetailsPanel extends m( Section, {title: 'Description'}, - m('.div', this.getDescriptionText()), + this.getDescriptionText(), ), // TODO: Add custom widgets (e.g. event latency table). )), diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts index 65f03ef14f..b91bbca071 100644 --- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts +++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts @@ -36,6 +36,7 @@ import {DurationWidget} from '../../widgets/duration'; import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout'; import {Section} from '../../widgets/section'; import {SqlRef} from '../../widgets/sql_ref'; +import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph'; import {dictToTreeNodes, Tree, TreeNode} from '../../widgets/tree'; import {EventLatencyTrack} from './event_latency_track'; @@ -239,15 +240,15 @@ export class ScrollJankV3DetailsPanel extends private getDescriptionText(): m.Child { return m( - `div[style='white-space:pre-wrap']`, - `Delay between when the frame was expected to be presented and when it - was actually presented.{new_lines} - - This is the period of time during which the user is viewing a frame - that isn't correct.`.replace(/\s\s+/g, ' ') - .replace(/{new_lines}/g, '\n\n') - .replace(' This', 'This'), - ); + MultiParagraphText, + m(TextParagraph, { + text: `Delay between when the frame was expected to be presented and + when it was actually presented.`, + }), + m(TextParagraph, { + text: `This is the period of time during which the user is viewing a + frame that isn't correct.`, + })); } private getLinksSection(): m.Child[] { diff --git a/ui/src/widgets/text_paragraph.ts b/ui/src/widgets/text_paragraph.ts new file mode 100644 index 0000000000..b61601e6e9 --- /dev/null +++ b/ui/src/widgets/text_paragraph.ts @@ -0,0 +1,58 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import m from 'mithril'; +import {classNames} from '../base/classnames'; + +export interface TextParagraphAttrs { + // Paragraph text. + text: string; + // Whether to compress multiple spaces (e.g. the string is multi-line but + // should render with the default UI wrapping) + compressSpace?: boolean; + // Remaining attributes forwarded to the underlying HTML
. + [htmlAttrs: string]: any; +} + +export class TextParagraph implements m.ClassComponent { + view({attrs}: m.CVnode) { + let {text, compressSpace} = attrs; + if (compressSpace === undefined) { + compressSpace = true; + } + return m( + `div.pf-text-paragraph`, + compressSpace ? text.replace(/\s\s+/g, ' ') : text); + } +} + +interface MultiParagraphTextAttrs { + // Space delimited class list applied to element. + className?: string; +} + +export class MultiParagraphText implements + m.ClassComponent { + view({attrs, children}: m.Vnode): m.Children { + const { + className = '', + } = attrs; + + const classes = classNames( + className, + ); + + return m('div', {class: classes}, children); + } +}