ui: add segmented buttons widget

Change-Id: Id8e80b90f2d24ebcf5ca706cf88dc6899f2ebd08
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index ca985ea..fdf03d2 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -49,6 +49,7 @@
 @import "widgets/multiselect";
 @import "widgets/popup";
 @import "widgets/section";
+@import "widgets/segmented_buttons";
 @import "widgets/select";
 @import "widgets/spinner";
 @import "widgets/switch";
diff --git a/ui/src/assets/widgets/segmented_buttons.scss b/ui/src/assets/widgets/segmented_buttons.scss
new file mode 100644
index 0000000..5f11e7e
--- /dev/null
+++ b/ui/src/assets/widgets/segmented_buttons.scss
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 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-segmented-buttons {
+  display: flex;
+  flex-direction: row;
+
+  .pf-button {
+    border-radius: 0;
+
+    &:first-child {
+      border-radius: $pf-border-radius 0 0 $pf-border-radius;
+    }
+
+    &:last-child {
+      border-radius: 0 $pf-border-radius $pf-border-radius 0;
+    }
+  }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 970b644..9b258b1 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -56,6 +56,7 @@
   VirtualTableRow,
 } from '../widgets/virtual_table';
 import {TagInput} from '../widgets/tag_input';
+import {SegmentedButtons} from '../widgets/segmented_buttons';
 
 const DATA_ENGLISH_LETTER_FREQUENCY = {
   table: [
@@ -628,6 +629,23 @@
   };
 }
 
+function SegmentedButtonsDemo({attrs}: {attrs: {}}) {
+  let selectedIdx = 0;
+  return {
+    view: () => {
+      return m(SegmentedButtons, {
+        ...attrs,
+        options: [{label: 'Yes'}, {label: 'Maybe'}, {label: 'No'}],
+        selectedOption: selectedIdx,
+        onOptionSelected: (num) => {
+          selectedIdx = num;
+          raf.scheduleFullRedraw();
+        },
+      });
+    },
+  };
+}
+
 export const WidgetsPage = createPage({
   view() {
     return m(
@@ -654,6 +672,17 @@
         },
       }),
       m(WidgetShowcase, {
+        label: 'Segmented Buttons',
+        description: `
+          Segmented buttons are a group of buttons where one of them is
+          'selected'; they act similar to a set of radio buttons.
+        `,
+        renderWidget: (opts) => m(SegmentedButtonsDemo, opts),
+        initialOpts: {
+          disabled: false,
+        },
+      }),
+      m(WidgetShowcase, {
         label: 'Checkbox',
         renderWidget: (opts) => m(Checkbox, {label: 'Checkbox', ...opts}),
         initialOpts: {
diff --git a/ui/src/widgets/segmented_buttons.ts b/ui/src/widgets/segmented_buttons.ts
new file mode 100644
index 0000000..9e8c533
--- /dev/null
+++ b/ui/src/widgets/segmented_buttons.ts
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 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 {Button} from './button';
+import {HTMLAttrs} from './common';
+
+interface IconOption {
+  // Icon buttons require an icon.
+  readonly icon: string;
+}
+
+interface LabelOption {
+  // Label buttons require a label.
+  readonly label: string;
+  // Label buttons can have an optional icon.
+  readonly icon?: string;
+}
+
+type Option = LabelOption | IconOption;
+
+export interface SegmentedButtonsAttrs extends HTMLAttrs {
+  // Options for segmented buttons.
+  readonly options: ReadonlyArray<Option>;
+
+  // The index of the selected button.
+  readonly selectedOption: number;
+
+  // Callback function which is called every time a
+  readonly onOptionSelected: (num: number) => void;
+
+  // Whether the segmented buttons is disabled.
+  // false by default.
+  readonly disabled?: boolean;
+}
+
+export class SegmentedButtons
+  implements m.ClassComponent<SegmentedButtonsAttrs>
+{
+  view({attrs}: m.CVnode<SegmentedButtonsAttrs>) {
+    const {options, selectedOption, disabled, onOptionSelected, ...htmlAttrs} =
+      attrs;
+    return m(
+      '.pf-segmented-buttons',
+      htmlAttrs,
+      options.map((o, i) =>
+        m(Button, {
+          ...o,
+          disabled: disabled,
+          active: i === selectedOption,
+          onclick: () => onOptionSelected(i),
+        }),
+      ),
+    );
+  }
+}