package main import engine "shared:zephr" // M3 common buttons, FABs and chips. // // Spec: common buttons are 40dp tall, fully rounded (20dp), labelLarge 14sp/500, // with 24dp horizontal padding -- 16dp on the leading side when an icon is // present, since the icon already supplies optical weight. M3_BUTTON_HEIGHT :: 40 M3_BUTTON_PAD_X :: 24 M3_BUTTON_PAD_X_ICON :: 16 M3_BUTTON_ICON_SIZE :: 18 // dp, smaller than an app bar icon M3_BUTTON_GAP :: 8 // dp, icon -> label M3_Button_Kind :: enum { Filled, // highest emphasis Tonal, // filled tonal Outlined, // medium emphasis, 1dp outline Elevated, // surfaceContainerLow, reads as lifted Text, // lowest emphasis, no container } @(private = "file") button_style :: proc(kind: M3_Button_Kind) -> engine.ButtonStyle { s: engine.ButtonStyle s.border_radius = M3_BUTTON_HEIGHT / 2 // fully rounded s.border_thickness = 0 s.padding_x = M3_BUTTON_PAD_X s.padding_y = 0 container, on_container: engine.Color switch kind { case .Filled: container, on_container = m3.primary, m3.on_primary case .Tonal: container, on_container = m3.secondary_container, m3.on_secondary_container case .Elevated: container, on_container = m3.surface_c_low, m3.primary case .Outlined: container, on_container = m3_alpha(m3.surface, 0), m3.primary s.border_thickness = 1 case .Text: container, on_container = m3_alpha(m3.surface, 0), m3.primary } s.background_idle = container // Every state is the container with an on-container layer over it -- the // spec defines states this way rather than naming separate colours. s.background_hover = m3_state_layer(container, on_container, M3_STATE_HOVER) s.background_active = m3_state_layer(container, on_container, M3_STATE_PRESSED) s.background_disabled = kind == .Text || kind == .Outlined \ ? m3_alpha(m3.surface, 0) \ : m3_alpha(m3.on_surface, M3_DISABLED_BG) outline := kind == .Outlined ? m3.outline : m3_alpha(m3.surface, 0) s.border_idle = outline s.border_hover = outline s.border_active = outline s.border_disabled = kind == .Outlined ? m3_alpha(m3.on_surface, M3_DISABLED_BG) : outline s.text_idle = on_container s.text_hover = on_container s.text_active = on_container s.text_disabled = m3_alpha(m3.on_surface, M3_DISABLED_FG) return s } m3_button :: proc(label: string, kind: M3_Button_Kind, id: engine.Id, enabled := true) -> bool { engine.ui_set_next_height(engine.ui_px(M3_BUTTON_HEIGHT, 1)) engine.ui_set_next_width(engine.ui_children_sum(1)) // push, not set_next: a button builds its container and content row before // the label box, so a set_next value never reaches the text. engine.ui_push_font_size(M3_LABEL_LARGE) defer engine.ui_pop_font_size() engine.ui_push_font_weight(M3_WEIGHT_MEDIUM) defer engine.ui_pop_font_weight() click, _ := engine.ui_button_styled(label, button_style(kind), enabled, id) return click } m3_button_icon :: proc(label, icon: string, kind: M3_Button_Kind, id: engine.Id, enabled := true) -> bool { st := button_style(kind) st.padding_x = M3_BUTTON_PAD_X_ICON st.icon_size = M3_BUTTON_ICON_SIZE // 18dp, per spec -- not the 14sp label size engine.ui_set_next_height(engine.ui_px(M3_BUTTON_HEIGHT, 1)) engine.ui_set_next_width(engine.ui_children_sum(1)) engine.ui_push_font_size(M3_LABEL_LARGE) defer engine.ui_pop_font_size() engine.ui_push_font_weight(M3_WEIGHT_MEDIUM) defer engine.ui_pop_font_weight() click, _ := engine.ui_button_styled_with_icons(label, st, icon, "", enabled, id) return click } // -- FAB --------------------------------------------------------------------- M3_Fab_Size :: enum { Small, // 40dp, 12dp radius Regular, // 56dp, 16dp radius Large, // 96dp, 28dp radius } m3_fab :: proc(icon: string, size: M3_Fab_Size, id: engine.Id) -> bool { dim, radius, icon_size: f32 switch size { case .Small: dim, radius, icon_size = 40, M3_SHAPE_MD, 24 case .Regular: dim, radius, icon_size = 56, M3_SHAPE_LG, 24 case .Large: dim, radius, icon_size = 96, M3_SHAPE_XL, 36 } s: engine.ButtonStyle // The primary-container FAB is the default of the three colour mappings. s.background_idle = m3.primary_container s.background_hover = m3_state_layer(m3.primary_container, m3.on_primary_container, M3_STATE_HOVER) s.background_active = m3_state_layer(m3.primary_container, m3.on_primary_container, M3_STATE_PRESSED) s.background_disabled = m3_alpha(m3.on_surface, M3_DISABLED_BG) s.text_idle = m3.on_primary_container s.text_hover = m3.on_primary_container s.text_active = m3.on_primary_container s.text_disabled = m3_alpha(m3.on_surface, M3_DISABLED_FG) s.border_radius = radius s.border_thickness = 0 s.padding_x = 0 s.padding_y = 0 engine.ui_set_next_width(engine.ui_px(dim, 1)) engine.ui_set_next_height(engine.ui_px(dim, 1)) engine.ui_push_font_size(i32(icon_size)) defer engine.ui_pop_font_size() click, _ := engine.ui_button_styled(icon, s, true, id) return click } // Extended FAB: 56dp tall, 16dp radius, icon + label, 16dp padding. m3_fab_extended :: proc(label, icon: string, id: engine.Id) -> bool { s: engine.ButtonStyle s.background_idle = m3.primary_container s.background_hover = m3_state_layer(m3.primary_container, m3.on_primary_container, M3_STATE_HOVER) s.background_active = m3_state_layer(m3.primary_container, m3.on_primary_container, M3_STATE_PRESSED) s.background_disabled = m3_alpha(m3.on_surface, M3_DISABLED_BG) s.text_idle = m3.on_primary_container s.text_hover = m3.on_primary_container s.text_active = m3.on_primary_container s.text_disabled = m3_alpha(m3.on_surface, M3_DISABLED_FG) s.border_radius = M3_SHAPE_LG s.border_thickness = 0 s.padding_x = 16 s.padding_y = 0 s.icon_size = 24 // spec FAB icon; the 14sp label must not drag it down engine.ui_set_next_height(engine.ui_px(56, 1)) engine.ui_set_next_width(engine.ui_children_sum(1)) engine.ui_push_font_size(M3_LABEL_LARGE) defer engine.ui_pop_font_size() engine.ui_push_font_weight(M3_WEIGHT_MEDIUM) defer engine.ui_pop_font_weight() click, _ := engine.ui_button_styled_with_icons(label, s, icon, "", true, id) return click } // -- Chips ------------------------------------------------------------------- M3_CHIP_HEIGHT :: 32 M3_CHIP_RADIUS :: M3_SHAPE_SM M3_CHIP_PAD_X :: 16 M3_CHIP_ICON_SIZE :: 18 // dp, per spec // Assist chip: outlined container, labelLarge, optional leading icon. m3_chip_assist :: proc(label, icon: string, id: engine.Id) -> bool { s: engine.ButtonStyle s.background_idle = m3_alpha(m3.surface, 0) s.background_hover = m3_state_layer(m3.surface, m3.on_surface, M3_STATE_HOVER) s.background_active = m3_state_layer(m3.surface, m3.on_surface, M3_STATE_PRESSED) s.background_disabled = m3_alpha(m3.surface, 0) s.border_idle = m3.outline s.border_hover = m3.outline s.border_active = m3.outline s.border_disabled = m3_alpha(m3.on_surface, M3_DISABLED_BG) s.text_idle = m3.on_surface s.text_hover = m3.on_surface s.text_active = m3.on_surface s.text_disabled = m3_alpha(m3.on_surface, M3_DISABLED_FG) s.border_radius = M3_CHIP_RADIUS s.border_thickness = 1 s.padding_x = M3_CHIP_PAD_X s.padding_y = 0 s.icon_size = M3_CHIP_ICON_SIZE engine.ui_set_next_height(engine.ui_px(M3_CHIP_HEIGHT, 1)) engine.ui_set_next_width(engine.ui_children_sum(1)) engine.ui_push_font_size(M3_LABEL_LARGE) defer engine.ui_pop_font_size() engine.ui_push_font_weight(M3_WEIGHT_MEDIUM) defer engine.ui_pop_font_weight() click, _ := engine.ui_button_styled_with_icons(label, s, icon, "", true, id) return click } // Filter chip: selection swaps the container to secondaryContainer, drops the // outline, and shows a leading check. Toggles `selected` in place. m3_chip_filter :: proc(label: string, selected: ^bool, id: engine.Id) -> bool { s: engine.ButtonStyle container := selected^ ? m3.secondary_container : m3_alpha(m3.surface, 0) on_container := selected^ ? m3.on_secondary_container : m3.on_surface_variant s.background_idle = container s.background_hover = m3_state_layer(container, on_container, M3_STATE_HOVER) s.background_active = m3_state_layer(container, on_container, M3_STATE_PRESSED) s.background_disabled = m3_alpha(m3.surface, 0) outline := selected^ ? m3_alpha(m3.surface, 0) : m3.outline s.border_idle = outline s.border_hover = outline s.border_active = outline s.border_disabled = outline s.text_idle = on_container s.text_hover = on_container s.text_active = on_container s.text_disabled = m3_alpha(m3.on_surface, M3_DISABLED_FG) s.border_radius = M3_CHIP_RADIUS s.border_thickness = selected^ ? 0 : 1 s.padding_x = M3_CHIP_PAD_X s.padding_y = 0 s.icon_size = M3_CHIP_ICON_SIZE engine.ui_set_next_height(engine.ui_px(M3_CHIP_HEIGHT, 1)) engine.ui_set_next_width(engine.ui_children_sum(1)) engine.ui_push_font_size(M3_LABEL_LARGE) defer engine.ui_pop_font_size() engine.ui_push_font_weight(M3_WEIGHT_MEDIUM) defer engine.ui_pop_font_weight() leading := selected^ ? ICON_CHECK : "" click, _ := engine.ui_button_styled_with_icons(label, s, leading, "", true, id) if click { selected^ = !selected^ } return click }