From f95d7cfc7d282a081f7dbb40a3a4b1f23b7f8373 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 4 Sep 2017 18:22:54 -0700 Subject: [PATCH 1/5] App skeletons and UI --- .gitignore | 1 + libraries/app/app.eve | 149 ++++++++++++++++ libraries/html/html.eve | 1 + libraries/ui/ui.eve | 381 +++++++++++++++++++++++++--------------- 4 files changed, 389 insertions(+), 143 deletions(-) create mode 100644 libraries/app/app.eve diff --git a/.gitignore b/.gitignore index b84b594..d9c9084 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/ dist/ .vscode/tasks.json examples/test.eve +*.log diff --git a/libraries/app/app.eve b/libraries/app/app.eve new file mode 100644 index 0000000..ba67c75 --- /dev/null +++ b/libraries/app/app.eve @@ -0,0 +1,149 @@ +# App + +This library provides a set of components to assist in quickly prototyping +single and multi page applications. + +## Components + +An app has three components: + +- `#app/settings` - Holds app-wide configuration data +- `#app/page` - A page of your application. Holds the name of the page, store + page data, and hold the UI of a page. +- `#app/ui` - Holds the layout of the application. UI stored on an `#app/page` + is injected into this layout for display. + +## Configuration + +The `#app/settings` record is a place to store app-wide configuration data. You +can attach any records you like here, and they will be available for viewing +and editing in the `#app/page/settings` record, which will be displayed by +default in your app. + +Attributes of `#app/settings`: + +- name - the name of your application. Will be displayed in the header +- settings - forms that allow you to view and edit configuration settings + of your app. + +## Pages + +`#app/page`s are the core of your application. These are the top-level sections +of your application, and will be accessible by a button in the navigation bar. +You can of course have sub pages as well. The navigation bar can be configured +to display these. + +Attributes of an `#app/page`: + +- name - Required: the name of the page +- icon - an optional icon for your page. This will be displayed on the nav bar. +- sort - an optoinal sorting for your page. Determines the order pages are + displayed in the navigation bar. + +## Layout + +An `#app/ui` has four basic components: + +- Header - Located at the top of the application, spanning the entire width +- Navigation - Contains controls that switch between pages in an application. + Can be located on any of the sides of an appliation. +- Content - Contains the pages of an application +- Footer - Located at the bottom of an application, spanning the entire width. + +search + app-ui = [#app/ui] +bind + app-ui.interface += [#app/layout #ui/column | children: + [#app/layout/header sort: 1] + [#app/layout/content sort: 3] + [#app/layout/footer sort: 5]] +end + +### Header + +The `#app/layout/header` spans the entire width of the top of the application. It +automatically displays the name of your application, and contains two +containers, `#app/layout/header/left` and `#app/layout/header/right` +for displaying controls at the top corners of your application. + +search + header = [#app/layout/header] + [#app/settings name] +bind + header <- [#ui/row | children: + [#html/div #app/layout/header/left sort: 1] + [#html/div #app/layout/header/middle sort: 2 text: name] + [#html/div #app/layout/header/right sort: 3]] +end + +### Content and Navigation + +search + content = [#app/layout/content] +bind + content <- [#ui/row | children: + [#app/layout/navigation sort: 1] + [#app/layout/page-container sort: 2]] +end + +#### Navigation + +search + nav = [#app/layout/navigation] + page = [#app/page name icon sort] +bind + nav <- [#ui/column | children: + [#ui/list #ui/selectable #ui/single-selectable #navigation/pages | item: + [#app/layout/navigation-button #ui/button icon page text: name sort]] + [#ui/spacer #nav-spacer sort: 99]] +end + +Clicking a navigation button switches the page. + +search + [#html/event/click element: [#app/layout/navigation-button page]] + ui = [#app/ui] +commit + ui.page := page +end + +#### Page Container + +search + page-container = [#app/layout/page-container] + [#app/ui page] +bind + page-container <- [#html/div #app/layout/page | children: page] +end + +### Footer + +The footer spans the entire width of the bottom of the application. It +contains three containers`#app/layout/footer/left`, `#app/layout/footer/middle` +and `#app/layout/footer/right`. + +search + header = [#app/layout/footer] +bind + header <- [#ui/row | children: + [#html/div #app/layout/footer/left sort: 1] + [#html/div #app/layout/footer/middle sort: 2 text: "Footer"] + [#html/div #app/layout/footer/right sort: 3]] +end + +## Styles + +commit + [#html/style text: " + .app-layout { background-color: rgb(255,255,255); width: 100vw; height: 100vh; color: rgb(80,80,80); font-family: sans-serif; user-select: none; cursor: default; display: flex; } + .app-layout-header { background-color: rgb(200,200,200); display: flex; justify-content: space-between; } + .app-layout-header-middle { padding: 10px; } + .app-layout-footer { background-color: rgb(200,200,200); display: flex; justify-content: space-between; } + .app-layout-footer-middle { padding: 10px; } + .app-layout-content {flex-grow: 1;} + .app-layout-page-container { padding: 10px; } + .app-layout-navigation { background-color: rgb(130,130,130); } + .app-layout-navigation-button { padding: 10px 20px 10px 20px; background-color: rgb(230,230,230); margin: 0px; border: 0px; border-radius: 0px; width: 100%; min-height: 75px; } + .app-layout-navigation-button.ui-selected { background-color: rgb(255,255,255); color: rgb(0,158,224); } + "] +end \ No newline at end of file diff --git a/libraries/html/html.eve b/libraries/html/html.eve index 033f365..5468826 100644 --- a/libraries/html/html.eve +++ b/libraries/html/html.eve @@ -118,6 +118,7 @@ search type != "checkbox" type != "submit" type != "radio" + type != "range" then input if input = [#html/element tagname: "input"] not(input.type) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 61e4e5b..565068c 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -2,17 +2,17 @@ ## Deprecation Warnings -~~~ eve + search [#html/shortcut-tag shortcut tagname] not([#ui/shortcut-tag tagname]) bind [#ui/deprecated-shortcut-tag shortcut: "ui/{{tagname}}" new-shortcut: shortcut tagname] end -~~~ + Report deprecated shortcuts as warnings. -~~~ eve + search [#ui/deprecated-shortcut-tag shortcut: tag new-shortcut tagname] element = [tag] @@ -20,12 +20,12 @@ bind [#eve/warning #ui/warning #eve/deprecated message: "The shortcut tag '#{{tag}}' for creating basic HTML elements has been deprecated. Instead, use '#{{new-shortcut}}'."] element <- [#html/element tagname] end -~~~ + ## Shortcut Tags -~~~ eve + commit [#ui/shortcut-tag shortcut: "ui/row" tagname: "row"] [#ui/shortcut-tag shortcut: "ui/column" tagname: "column"] @@ -34,51 +34,51 @@ commit [#ui/shortcut-tag shortcut: "ui/button" tagname: "button"] [#ui/shortcut-tag shortcut: "ui/input" tagname: "input"] end -~~~ + Decorate shortcut elements as html. -~~~ eve + search [#ui/shortcut-tag shortcut: tag tagname] element = [tag] bind element <- [#html/element tagname] end -~~~ + ## General Setup Clear ui events. -~~~ eve + search event = [#ui/event] commit event := none end -~~~ + Translate bubble event names to event tags. -~~~ eve + search bubble = [#ui/bubble-event event: name] bind bubble.event-tag += "ui/event/{{name}}" end -~~~ + Bubble ui events. -~~~ eve + search event = [#ui/event tag: event-tag element: from] [#ui/bubble-event event-tag from to] bind event.element += to end -~~~ + Translate state/start/stop event names to event tags. -~~~ eve + search transition = [#ui/state-tag state start-event stop-event] bind @@ -86,46 +86,46 @@ bind transition.start-tag += "ui/event/{{start-event}}" transition.stop-tag += "ui/event/{{stop-event}}" end -~~~ + Apply state when start-event occurs. -~~~ eve + search [#ui/event tag: start-tag element: for] [#ui/state-tag for state-tag start-tag] commit for.tag += state-tag end -~~~ + Remove state when stop-event occurs. -~~~ eve + search [#ui/event tag: stop-tag element: for] [#ui/state-tag for state-tag stop-tag] commit for.tag -= state-tag end -~~~ + ## Buttons Give button elements icons if specified. -~~~ eve + search element = [#ui/button icon] bind element.class += "iconic" element.class += "ion-{{icon}}" end -~~~ + ## Toggle A toggle is a checkbox and a label decorated as a toggle switch. -~~~ eve + search element = [#ui/toggle] bind @@ -133,20 +133,20 @@ bind [#html/element tagname: "label" for: "ui-toggle-{{element}}"] [#html/input #ui/toggle/input type: "checkbox" id: "ui-toggle-{{element}}"]] end -~~~ + Copy checked from input to toggle. -~~~ eve + search element = [#ui/toggle children: [#ui/toggle/input checked]] bind element.checked += checked end -~~~ + Copy initial from toggle to input. -~~~ eve + search element = [#ui/toggle initial children: input] input = [#ui/toggle/input] @@ -154,33 +154,33 @@ search bind input.initial += initial end -~~~ + ## List Decorate list as html. -~~~ eve + search list = [#ui/list] bind list <- [#html/element tagname: "div"] end -~~~ + Drop items into list (will eventually be flywheel'd). -~~~ eve + search list = [#ui/list item] bind list.children += item end -~~~ + ## Selectable The default cursor for a selectable list is the first item. -~~~ eve + search selectable = [#ui/selectable item] not(selectable.cursor) @@ -188,30 +188,30 @@ search commit selectable.cursor := item end -~~~ + If the cursor is no longer an item, clear it. -~~~ eve + search selectable = [#ui/selectable cursor] not(selectable = [#ui/selectable item: cursor]) commit selectable.cursor := none end -~~~ + Selectable items are sorted by autosort if they don't specify a sort. -~~~ eve + search selectable = [#ui/selectable item] sort = if s = item.sort then s else item.eve-auto-index bind item.sort += sort end -~~~ + Build a linked list of the items in the selectable for navigation. -~~~ eve + search selectable = [#ui/selectable item] (next-sort next) = gather/next[for: (item.sort item) per: selectable] @@ -219,71 +219,71 @@ bind item.next-selectable-item += next next.prev-selectable-item += item end -~~~ + Mark the currently selected element. -~~~ eve + search [#ui/selectable selected] bind selected += #ui/selected end -~~~ + Mark the cursor element. -~~~ eve + search [#ui/selectable #ui/active cursor] bind cursor += #ui/current end -~~~ + A focused selectable is active. -~~~ eve + search selectable = [#ui/selectable #html/focused] bind selectable += #ui/active end -~~~ + ### Handlers If a selectable is focused by the client, activate it. -~~~ eve + search selectable = [#ui/selectable] [#html/event/focus element: selectable] bind [#ui/event #ui/event/activate element: selectable] end -~~~ + If a selectable is blurred by the client, deactivate it. -~~~ eve + search selectable = [#ui/selectable] [#html/event/blur element: selectable] bind [#ui/event #ui/event/deactivate element: selectable] end -~~~ + Clicking an item in a selectable selects the item. -~~~ eve + search selectable = [#ui/selectable item] [#html/event/mouse-down element: item] bind [#ui/event #ui/event/select element: selectable item] end -~~~ + Clicking outside an active selectable deactivates it. @FIXME: This just won't work :( It clashes when used as a subcomponent. -~~~ eve + // search // selectable = [#ui/selectable #ui/active] // [#html/event/click] @@ -292,10 +292,10 @@ Clicking outside an active selectable deactivates it. // bind // [#ui/event #ui/event/deactivate element: selectable] // end -~~~ + Escape or tab in a active selectable deactivates it. -~~~ eve + search selectable = [#ui/selectable #ui/active] event = if e = [#html/event/key-down key: "escape"] then e @@ -304,124 +304,124 @@ search bind [#ui/event #ui/event/deactivate element: selectable] end -~~~ + Enter in a active selectable selects its cursor. -~~~ eve + search selectable = [#ui/selectable #ui/active cursor:item] [#html/event/key-down key: "enter"] bind [#ui/event #ui/event/select element: selectable item] end -~~~ + Down in a active selectable advances the cursor. -~~~ eve + search selectable = [#ui/selectable #ui/active cursor] event = [#html/event/key-down key: "down"] commit selectable.cursor := cursor.next-selectable-item end -~~~ + Up in a active selectable retreats the cursor. -~~~ eve + search selectable = [#ui/selectable #ui/active cursor] [#html/event/key-down key: "up"] commit selectable.cursor := cursor.prev-selectable-item end -~~~ + ### Events Describe selectable states. -~~~ eve + search selectable = [#ui/selectable] bind [#ui/state-tag for: selectable state: "active" start-event: "activate" stop-event: "deactivate"] end -~~~ + Selecting an element updates the selected and cursor. -~~~ eve + search event = [#ui/event/select element: selectable item] commit selectable.selected += item selectable.cursor := item end -~~~ + In a single-selectable selectable, selection overwrites the previous selected. -~~~ eve + search event = [#ui/event/select element: selectable item] selectable = [#ui/single-selectable] commit selectable.selected := item end -~~~ + Clearing a selectable clears its selected. -~~~ eve + search event = [#ui/event/clear element: selectable] commit selectable.selected := none end -~~~ + ### Dropdown Decorate dropdown as html. -~~~ eve + search dropdown = [#ui/dropdown] bind dropdown <- [#html/element tagname: "div"] end -~~~ + A dropdown's first child is its input. -~~~ eve + search dropdown = [#ui/dropdown input] bind dropdown.children += input input.sort += 1 end -~~~ + A dropdown creates a selectable list of its items. -~~~ eve + search dropdown = [#ui/dropdown item] bind dropdown.children += [#ui/dropdown/list #ui/list #ui/selectable #ui/single-selectable dropdown container: dropdown sort: 999999 | item] end -~~~ + A dropdown's selected is its list's -~~~ eve + search [#ui/dropdown/list dropdown selected] bind dropdown.selected += selected end -~~~ + ### Handlers Clicking a dropdown opens it. -~~~ eve + search dropdown = [#ui/dropdown] not(dropdown = [#ui/active]) @@ -429,10 +429,10 @@ search bind [#ui/event #ui/event/activate element: dropdown] end -~~~ + Clicking anywhere outside an open dropdown closes it. -~~~ eve + search dropdown = [#ui/dropdown #ui/active] [#html/event/click] @@ -440,13 +440,13 @@ search bind [#ui/event #ui/event/deactivate element: dropdown] end -~~~ + ### Events Describe dropdown event bubbling and states. -~~~ eve + search dropdown = [#ui/dropdown input] list = [#ui/dropdown/list dropdown] @@ -455,83 +455,83 @@ bind [#ui/bubble-event from: list to: dropdown event: ("activate" "deactivate" "clear" "select")] [#ui/state-tag for: dropdown state: "active" start-event: "activate" stop-event: "deactivate"] end -~~~ + Opening a button-based dropdown blurs the button. -~~~ eve + search [#ui/event/activate element: dropdown] dropdown.input.tagname = "button" commit dropdown.input += #html/trigger/blur end -~~~ + ## Completer Decorate completer. -~~~ eve + search completer = [#ui/completer] bind completer <- [#ui/dropdown input: [#ui/input #ui/completer/input completer]] end -~~~ + ### Setup Copy input placeholder. -~~~ eve + search input = [#ui/completer/input completer] bind input.placeholder += completer.placeholder end -~~~ + Copy input initial. -~~~ eve + search input = [#ui/completer/input completer] bind input.initial += completer.initial end -~~~ + A completer's value is its input's. -~~~ eve + search completer = [#ui/completer] value = if [#ui/completer/input completer value: v] then v else "" bind completer.value += value end -~~~ + ### Handlers Focusing the completer opens the dropdown. -~~~ eve + search input = [#ui/completer/input completer] [#html/event/focus element: input] bind [#ui/event #ui/event/activate element: completer] end -~~~ + Blurring the completer closes the dropdown. -~~~ eve + search input = [#ui/completer/input completer] [#html/event/blur element: input] bind [#ui/event #ui/event/deactivate element: completer] end -~~~ + Changing the completer moves the cursor to the top of the list. -~~~ eve + search completer = [#ui/completer #ui/active item] list = [#ui/dropdown/list dropdown: completer] @@ -541,57 +541,57 @@ search commit list.cursor := item end -~~~ + ### Events Closing a completer blurs it. -~~~ eve + search [#ui/event/deactivate element: completer] completer = [#ui/completer input] commit input += #html/trigger/blur end -~~~ + Opening a completer focuses it. -~~~ eve + search [#ui/event/activate element: completer] completer = [#ui/completer input] commit input += #html/trigger/focus end -~~~ + ## Autocomplete Decorate autocomplete -~~~ eve + search completer = [#ui/autocomplete] bind completer <- [#ui/completer] end -~~~ + ### Logic If an autocomplete's value disagrees with its selected, clear the selected. -~~~ eve + search completer = [#ui/autocomplete value: term selected] selected.text != term commit [#ui/event #ui/event/clear element: completer] end -~~~ + Completions that match the current input value are matches, sorted by length. -~~~ eve + search completer = [#ui/autocomplete value: term completion] ix = string/index-of[text: completion.text substring: string/lowercase[text: term]] @@ -600,12 +600,12 @@ bind completer.item += completion completion.sort += "{{sort}}{{completion.text}}" end -~~~ + ### Handlers If the value matches perfectly on blur, select that match. -~~~ eve + search input = [#ui/completer/input completer] completer = [#ui/autocomplete] @@ -616,33 +616,33 @@ search bind [#ui/event #ui/event/select element: completer item: match] end -~~~ + Autocompletes update their values on select. -~~~ eve + search autocomplete = [#ui/autocomplete input] [#ui/event/select item element: autocomplete] commit input.value := item.text end -~~~ + ### Events Clear the specified autocomplete. -~~~ eve + search event = [#ui/event/clear element: autocomplete] input = [#ui/autocomplete/input autocomplete] commit input.value := none end -~~~ + When an autocomplete is opened, store its previous value. -~~~ eve + search event = [#ui/event/activate element: autocomplete] input = [#ui/autocomplete/input autocomplete] @@ -650,74 +650,74 @@ search commit autocomplete.previous := value end -~~~ + When an autocomplete is closed, erase its previous value. -~~~ eve + search event = [#ui/event/deactivate element: autocomplete] input = [#ui/autocomplete/input autocomplete] commit autocomplete.previous := none end -~~~ + When an autocomplete is closed and its value is changed, emit a change event. -~~~ eve + search event = [#ui/event/deactivate element: autocomplete] autocomplete.value != autocomplete.previous commit [#ui/event #ui/event/change element: autocomplete value: autocomplete.value] end -~~~ + When a selection is made that differs from the previous value, emit a change event. -~~~ eve + search event = [#ui/event/select element: autocomplete item] item.text != autocomplete.previous commit [#ui/event #ui/event/change element: autocomplete value: item.text] end -~~~ + ## Token Completer Token completers are completers. -~~~ eve + search completer = [#ui/token-completer] bind completer <- [#ui/completer #html/listener/key | captured-key: ("space" "up" "down")] end -~~~ + Token items are divs. -~~~ eve + search completer = [#ui/token-completer item] bind item += #html/div end -~~~ + ### Logic The current term is the last word of the value. -~~~ eve + search completer = [#ui/token-completer value] (token, 1) = eve-internal/string/split-reverse[text: value by: " "] bind completer.needle += token end -~~~ + Completions that match the current input value are matches, sorted by length. -~~~ eve + search completer = [#ui/token-completer needle: term completion] ix = string/index-of[text: string/lowercase[text: completion.text] substring: string/lowercase[text: term]] @@ -726,32 +726,32 @@ bind completer.item += completion completion.sort += "{{sort}}{{completion.text}}" end -~~~ + Space-separated words are tokens of the completer. -~~~ eve + search completer = [#ui/token-completer value] (token, ix) = string/split[text: value by: " "] bind completer.token += [#ui/token-completer/token token ix] end -~~~ + Track the index of the last token. -~~~ eve + search completer = [#ui/token-completer token: [ix]] gather/top[for: ix per: completer limit: 1] bind completer.last-ix += ix end -~~~ + ### Handlers Token completers append the new completed token in place of the in progress token on select. -~~~ eve + search event = [#ui/event/select element: completer item] input = [#ui/completer/input completer] @@ -762,10 +762,10 @@ search commit input.value := "{{value}}{{item.text}} " end -~~~ + Token completers without an in-progress token just append the new one. -~~~ eve + search event = [#ui/event/select element: completer item] input = [#ui/completer/input completer] @@ -773,10 +773,10 @@ search commit input.value := "{{completer.value}}{{item.text}} " end -~~~ + Space in a active selectable selects its cursor. -~~~ eve + search completer = [#ui/token-completer #ui/active] list = [#ui/dropdown/list dropdown: completer cursor:item] @@ -784,9 +784,104 @@ search bind [#ui/event #ui/event/select element: list item] end -~~~ +## Progress + +search + progress = [#ui/progress min max value width] + val = if value >= max then max else value + //percentage = val / (max - min) + //percentage-display = math/to-fixed[value: percentage * 100 to: 0] + //progress-width = width * val / (max - min) + //display = if progress = [#label] then "inline" + // else "none" +bind + progress <- [#ui/row children: + [#html/div progress sort: 2 text: value] + // [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] + [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"]]] + // [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] +end + +Progress bars at 100% are marked `#ui/progress/complete` + +search + progress = [#ui/progress max value] + max = value +bind + progress += #ui/progress/complete +end + +## Slider + +search + slider = [#ui/slider min max] + initial-value = if slider.initial-value then slider.initial-value else min +bind + slider <- [#ui/input type: "range" initial-value] +end + +If a slider as an `initial-value` but no calculated `value`, set it + +search + slider = [#ui/slider initial-value] + not(slider.value) +commit + slider.value := initial-value +end + +An initial value out of range of the slider is set to its closest extreme and a warning is issued + +search + slider = [#ui/slider min max] + value = if slider.value > max then max + else if slider.value < min then min +commit + slider.value := value + [#console/warn text: "Value of slider ({{slider.initial-value}}) is outside of the bounds of the slider"] +end + +When the user changes the slider value, update its `value` in Eve + +search + [#html/event/change element: slider value] + slider = [#ui/slider] +commit + slider.value := value +end + +## Styles + +commit + [#html/style text: " + .ui-progress {margin: 10px;} + .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} + .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } + .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; transition: width 0.5s; } + input[type=range]{ -webkit-appearance: none; } + + input[type=range]::-webkit-slider-runnable-track { + width: 300px; + height: 7px; + background: #ccc; + border: none; + border-radius: 3px; + } + + input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: rgb(0,158,224); + margin-top: -5px; + } + input[type=range]:focus { outline: none; } + input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } + "] +end Todo: @@ -801,7 +896,7 @@ Todo: - list (navigable) - [ ] container - card -- [ ] progress +- [x] progress - [ ] Tree - [ ] chip - [ ] date picker From a7cb593bcb478bd9888999c1e69b407cc4ceb140 Mon Sep 17 00:00:00 2001 From: cmontella Date: Mon, 4 Sep 2017 18:29:43 -0700 Subject: [PATCH 2/5] fix sliders --- libraries/ui/ui.eve | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 565068c..2e1302c 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -790,18 +790,17 @@ end search progress = [#ui/progress min max value width] - val = if value >= max then max else value - //percentage = val / (max - min) - //percentage-display = math/to-fixed[value: percentage * 100 to: 0] - //progress-width = width * val / (max - min) - //display = if progress = [#label] then "inline" - // else "none" + val = if value > max then max else value + percentage = val / (max - min) + percentage-display = math/to-fixed[value: percentage * 100 to: 0] + progress-width = width * val / (max - min) + display = if progress = [#label] then "inline" + else "none" bind - progress <- [#ui/row children: - [#html/div progress sort: 2 text: value] - // [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] - [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"]]] - // [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] + progress <- [#ui/row | children: + [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] + [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"] children: + [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] end Progress bars at 100% are marked `#ui/progress/complete` @@ -847,8 +846,9 @@ When the user changes the slider value, update its `value` in Eve search [#html/event/change element: slider value] slider = [#ui/slider] + numeric-value = eve/parse-value[value] commit - slider.value := value + slider.value := numeric-value end ## Styles @@ -858,7 +858,7 @@ commit .ui-progress {margin: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } - .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; transition: width 0.5s; } + .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; } input[type=range]{ -webkit-appearance: none; } input[type=range]::-webkit-slider-runnable-track { From c013f404856cebc12acc40724e31d6b10dde3a84 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 10:25:39 -0700 Subject: [PATCH 3/5] resizeable spinner and tabbed box --- libraries/ui/ui.eve | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 2e1302c..cfe81f6 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -851,10 +851,95 @@ commit slider.value := numeric-value end +## Tabbed Box + +search + tab-container = [#ui/tabbed-box tabs] +bind + tab-container <- [#html/div children: + [#ui/tabbed-box/tab-row #ui/row | children: + [#html/div #ui/tabbed-box/tab-display tab-container tab: tabs sort: tabs.sort, text: tabs.title]] + [#ui/tabbed-box/content tabs tab-container]] +end + +Sort tabs by `title` if no sort is specified + +search + tab = [#ui/tabbed-box/tab title] + sort = if tab.sort then tab.sort else tab.title +commit + tab.sort := sort +end + +Inject `#selected` tab contents into the `#ui/tabbed-box` for display + +search + content-container = [#ui/tabbed-box/content tabs: [#ui/tabbed-box/tab #selected title]] +bind + content-container <- [#html/div text: title] +end + +If no tabs are `#selected`, then the first tab is `#selected` + + +## Spinner + +search + spinner = [#ui/spinner size] + radius = "{{size}}px" + diameter = "{{size * 2}}px" + border = "{{size / 4}}px solid rgb(0,158,224)" + +bind + [#html/div text: size] + spinner <- [#html/div #top #rotate | style: [ + height: radius + width: diameter + border-top-left-radius: diameter + border-top-right-radius: diameter + border-left: border + border-right: border + border-top: border + ]] +end + +Give spinners a default size if none is specified + +search + spinner = [#ui/spinner] + not(spinner = [size]) +commit + spinner.size += 10 +end + +Remove the default size if one is set adter the fact + +search + spinner = [#ui/spinner size != 25] +commit + spinner.size -= 25 +end + +Clicking on a tab changes the selected tab + +search + [#html/event/click element: [#ui/tabbed-box/tab-display tab-container tab: clicked-tab]] + [#ui/tabbed-box/tab-display tab: selected-tab] + selected-tab = [#selected] + selected-tab != clicked-tab +commit + selected-tab -= #selected + clicked-tab += #selected +end + ## Styles commit [#html/style text: " + .ui-tabbed-box {border: 1px solid rgb(230,230,230);} + .ui-tabbed-box-tab-display { background-color: rgb(230,230,230); padding: 20px;} + .ui-tabbed-box-tab-display:hover { background-color: rgb(240,240,240); } + .ui-tabbed-box-content {padding: 20px;} .ui-progress {margin: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } @@ -880,6 +965,30 @@ commit } input[type=range]:focus { outline: none; } input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } + +.top { + background-color: rgba(0,0,0,0); +} + +.rotate { + animation: 1s linear infinite rotate; + position: relative; + transform-origin: 50% 100%; +} + +@keyframes rotate { + from { + transform: rotate(0deg) + } + to { + transform: rotate(360deg) + } +} + + + + + "] end From 5e9c99eb5f62e6be0a6ea7901dc3f6d6a6fa5b1f Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 15:02:15 -0700 Subject: [PATCH 4/5] fix tabbed box displaying extra content --- libraries/ui/ui.eve | 101 ++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index cfe81f6..0b4a1a5 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -853,34 +853,57 @@ end ## Tabbed Box +Draw the `#ui/tabbed-box` + search tab-container = [#ui/tabbed-box tabs] bind - tab-container <- [#html/div children: + tab-container <- [#html/div | children: [#ui/tabbed-box/tab-row #ui/row | children: - [#html/div #ui/tabbed-box/tab-display tab-container tab: tabs sort: tabs.sort, text: tabs.title]] - [#ui/tabbed-box/content tabs tab-container]] + [#html/div #ui/tabbed-box/tab-display tab: tabs sort: tabs.title text: tabs.title]] + [#ui/tabbed-box/content-display tabs container: tab-container]] end -Sort tabs by `title` if no sort is specified +Give a default tag search - tab = [#ui/tabbed-box/tab title] - sort = if tab.sort then tab.sort else tab.title + content = [#ui/tabbed-box/content-display tabs] + gather/bottom[for: tabs limit: 1] commit - tab.sort := sort + tabs += #selected end -Inject `#selected` tab contents into the `#ui/tabbed-box` for display +Apply the selected tag to the `tag-display` for special styling search - content-container = [#ui/tabbed-box/content tabs: [#ui/tabbed-box/tab #selected title]] + tab = [#ui/tabbed-box/tab #selected title] + display = [#ui/tabbed-box/tab-display tab] bind - content-container <- [#html/div text: title] + display += #selected end -If no tabs are `#selected`, then the first tab is `#selected` +Inject tab content into the content are of thet `#ui/tabbed-box` +search + content-display = [#ui/tabbed-box/content-display tabs] + tabs = [#ui/tabbed-box/tab #selected] + [#ui/tabbed-box/content tab: tabs content] +bind + content-display <- [#html/div #ui/tabbed-box/has-content] + content-display.children += content +end + +Clicking on a tab changes the selected tab + +search + [#html/event/click element: [#ui/tabbed-box/tab-display tab: clicked-tab]] + [#ui/tabbed-box/tab-display tab: selected-tab] + selected-tab = [#selected] + selected-tab != clicked-tab +commit + selected-tab -= #selected + clicked-tab += #selected +end ## Spinner @@ -891,16 +914,14 @@ search border = "{{size / 4}}px solid rgb(0,158,224)" bind - [#html/div text: size] - spinner <- [#html/div #top #rotate | style: [ + spinner <- [#html/div #ui/spinner/circle #ui/spinner/rotate | style: [ height: radius width: diameter border-top-left-radius: diameter border-top-right-radius: diameter border-left: border border-right: border - border-top: border - ]] + border-top: border]] end Give spinners a default size if none is specified @@ -920,26 +941,15 @@ commit spinner.size -= 25 end -Clicking on a tab changes the selected tab - -search - [#html/event/click element: [#ui/tabbed-box/tab-display tab-container tab: clicked-tab]] - [#ui/tabbed-box/tab-display tab: selected-tab] - selected-tab = [#selected] - selected-tab != clicked-tab -commit - selected-tab -= #selected - clicked-tab += #selected -end - ## Styles commit [#html/style text: " .ui-tabbed-box {border: 1px solid rgb(230,230,230);} .ui-tabbed-box-tab-display { background-color: rgb(230,230,230); padding: 20px;} + .ui-tabbed-box-tab-display.selected { border-bottom: 3px solid rgb(0,158,224);} .ui-tabbed-box-tab-display:hover { background-color: rgb(240,240,240); } - .ui-tabbed-box-content {padding: 20px;} + .ui-tabbed-box-content-display {padding: 20px;} .ui-progress {margin: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } @@ -966,29 +976,17 @@ commit input[type=range]:focus { outline: none; } input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } -.top { - background-color: rgba(0,0,0,0); -} - -.rotate { - animation: 1s linear infinite rotate; - position: relative; - transform-origin: 50% 100%; -} - -@keyframes rotate { - from { - transform: rotate(0deg) - } - to { - transform: rotate(360deg) - } -} - - - - + .ui-spinner-circle { background-color: rgba(0,0,0,0); margin: 10px; } + .ui-spinner-rotate { animation: 1s linear infinite ui-spinner-rotate; position: relative; transform-origin: 50% 100%; } + @keyframes ui-spinner-rotate { + from { + transform: rotate(0deg) + } + to { + transform: rotate(360deg) + } + } "] end @@ -999,13 +997,14 @@ Todo: - [x] Dropdown - [x] *bug* commit removal not working for enter / click (blurs list -> closes dropdown) - [x] *bug* gather/bottom filtering randomly (always includes correct answer + optionally any others in set). -- [ ] Tab Box +- [x] Tab Box - [ ] Rewrite AC on Dropdown - input - list (navigable) - [ ] container - card - [x] progress +- [x] spinner - [ ] Tree - [ ] chip - [ ] date picker From f37c3b35f116957cdc2bd14b7aa4a0f664103e13 Mon Sep 17 00:00:00 2001 From: cmontella Date: Tue, 5 Sep 2017 17:28:07 -0700 Subject: [PATCH 5/5] draw a fancy slider --- libraries/ui/ui.eve | 65 +++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/libraries/ui/ui.eve b/libraries/ui/ui.eve index 0b4a1a5..577c194 100644 --- a/libraries/ui/ui.eve +++ b/libraries/ui/ui.eve @@ -794,13 +794,13 @@ search percentage = val / (max - min) percentage-display = math/to-fixed[value: percentage * 100 to: 0] progress-width = width * val / (max - min) - display = if progress = [#label] then "inline" - else "none" + display = if progress = [#label] then "inline" else "none" + color = if progress.color then progress.color else "rgb(111, 165, 81)" bind progress <- [#ui/row | children: [#html/div #ui/progress/label progress | sort: 2 text: "{{percentage-display}}%" style: [display]] [#html/div #ui/progress/container progress | sort: 1 style: [width: "{{width}}px"] children: - [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px"]]]] + [#html/div #ui/progress/bar progress | style: [width: "{{progress-width}}px" background-color: color]]]] end Progress bars at 100% are marked `#ui/progress/complete` @@ -815,19 +815,21 @@ end ## Slider search - slider = [#ui/slider min max] + slider = [#ui/slider min max width] initial-value = if slider.initial-value then slider.initial-value else min bind - slider <- [#ui/input type: "range" initial-value] + slider <- [#html/div children: [#ui/input min max slider type: "range" initial-value style: [width: "{{width}}px"]]] end If a slider as an `initial-value` but no calculated `value`, set it search slider = [#ui/slider initial-value] + input = [#ui/input slider] not(slider.value) commit slider.value := initial-value + input.value := initial-value end An initial value out of range of the slider is set to its closest extreme and a warning is issued @@ -841,16 +843,40 @@ commit [#console/warn text: "Value of slider ({{slider.initial-value}}) is outside of the bounds of the slider"] end -When the user changes the slider value, update its `value` in Eve +When the user changes the slider value in the UI, update its `value` in Eve search - [#html/event/change element: slider value] - slider = [#ui/slider] + [#html/event/change element: input value] + input = [#ui/input slider] numeric-value = eve/parse-value[value] commit slider.value := numeric-value end +Sliders without a width are given a default + +search + slider = [#ui/slider] + not(slider = [width]) +commit + slider.width := 400 +end + +Draw a progress bar under the slider + +search + slider = [#ui/slider min max value width] +bind + slider.children += [#ui/progress #slider-progress slider min max width sort: 0 | color: "rgb(0,121,177)" value] +end + +search + [#ui/slider value] +bind + [#html/div text: value] +end + + ## Tabbed Box Draw the `#ui/tabbed-box` @@ -950,31 +976,30 @@ commit .ui-tabbed-box-tab-display.selected { border-bottom: 3px solid rgb(0,158,224);} .ui-tabbed-box-tab-display:hover { background-color: rgb(240,240,240); } .ui-tabbed-box-content-display {padding: 20px;} - .ui-progress {margin: 10px;} + .ui-progress {padding: 10px;} .ui-progress-label {font-size: 14px; margin-top: -2px; margin-left: 4px;} .ui-progress-container { background-color: rgb(200,200,200); height: 10px; border-radius: 5px; } - .ui-progress-bar { background-color: green; height: 100%; border-radius: 5px; } - input[type=range]{ -webkit-appearance: none; } - + .ui-progress-bar { height: 100%; border-radius: 5px; } + input[type=range]{ -webkit-appearance: none; background-color: rgba(0,0,0,0);} + .ui-slider {margin: 10px; margin-left: 8px; } + .slider-progress {padding: 0px; padding-left: 2px; margin-bottom: -13px; } input[type=range]::-webkit-slider-runnable-track { - width: 300px; - height: 7px; - background: #ccc; + height: 10px; + background: rgba(0,0,0,0); border: none; - border-radius: 3px; + border-radius: 5px; } input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; - border: none; - height: 16px; - width: 16px; + height: 20px; + width: 20px; border-radius: 50%; background: rgb(0,158,224); margin-top: -5px; } input[type=range]:focus { outline: none; } - input[type=range]:focus::-webkit-slider-runnable-track { background: #ccc; } + input[type=range]:focus::-webkit-slider-runnable-track { background: rgba(0,0,0,0); } .ui-spinner-circle { background-color: rgba(0,0,0,0); margin: 10px; } .ui-spinner-rotate { animation: 1s linear infinite ui-spinner-rotate; position: relative; transform-origin: 50% 100%; }