diff --git a/index.html b/index.html index 53df449..3b5e719 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,7 @@ + diff --git a/src/CacheSim/View.elm b/src/CacheSim/View.elm index 38847b6..b83ee8f 100644 --- a/src/CacheSim/View.elm +++ b/src/CacheSim/View.elm @@ -4,30 +4,68 @@ import CacheSim.Model exposing (..) import CacheSim.Cache exposing (..) import CacheSim.AccessView exposing (..) import CacheSim.Hierarchy exposing (..) -import Html exposing (Html, input, text, div, label, span, h2, h3, table, tr, td) -import Html.Attributes exposing (type_, class, value, for, classList, disabled, colspan) +import Html exposing (Html, Attribute, input, text, div, label, span, h2, h3, table, tr, td, th) +import Html.Attributes exposing (type_, class, value, for, classList, disabled, colspan, hidden) import Html.Events exposing (onInput, onClick) -optionalButton : Bool -> String -> Msg -> Html Msg -optionalButton e s m = - let - events = if e then [ onClick m ] else [ disabled (not e) ] - in - input ([ type_ "button", value s ] ++ events) [] +-- Button components, powered by Bootstrap +basicButton : List (Attribute Msg) -> String -> Html Msg +basicButton attrs s = input ([ type_ "button", value s, class "btn", class "btn-info" ] ++ attrs) [] + +disabledButton : String -> Html Msg +disabledButton = basicButton [ disabled True ] + +advancedButton : List (Attribute Msg) -> String -> Msg -> Html Msg +advancedButton attrs s m = basicButton (attrs ++ [ onClick m ]) s button : String -> Msg -> Html Msg -button s m = input [ type_ "button", onClick m, value s] [] +button s m = basicButton [ onClick m ] s +dangerButton : String -> Msg -> Html Msg +dangerButton = advancedButton [ class "btn-danger" ] + +primaryButton : String -> Msg -> Html Msg +primaryButton = advancedButton [ class "btn-primary" ] + +secondaryButton : String -> Msg -> Html Msg +secondaryButton = advancedButton [ class "btn-secondary" ] + +maybeButton : Maybe a -> String -> (a -> Msg) -> Html Msg +maybeButton m s f = + case m of + Just v -> button s (f v) + _ -> disabledButton s + +resultButton : Result e a -> String -> (a -> Msg) -> Html Msg +resultButton = maybeButton << Result.toMaybe + +-- Button wrapper (button group) buttonWrapper : List (Html Msg) -> Html Msg -buttonWrapper = div [ class "button-wrapper" ] +buttonWrapper = div [ classList [("btn-group", True), ("mb-3", True) ] ] +-- Input with a label labeledInput : String -> String -> (String -> Msg) -> Html Msg labeledInput s val f = - div [ class "input-group" ] - [ span [] [ text s ] - , input [ value val, type_ "text", onInput f ] [] + div [ class "input-group mb-3" ] + [ div [ class "input-group-prepend" ] + [ span [ class "input-group-text" ] [ text s ] + ] + , input [ value val, type_ "text", class "form-control", onInput f ] [] ] +-- Error view +viewError : Bool -> String -> Html Msg +viewError hide e = div + [ classList + [ ("alert", True) + , ("alert-danger", True) + ] + , hidden hide + ] [ text e ] + +panel : List (Html Msg) -> Html Msg +panel = div [ classList [("card", True), ("p-3", True), ("mb-3", True) ] ] + viewRawCacheModel : Int -> RawCacheModel -> Html Msg viewRawCacheModel level rcm = let @@ -36,15 +74,15 @@ viewRawCacheModel level rcm = updateSetSize s cm = { cm | setSize = s} wrapUpdate f s = ChangeRawModel level (f s) - deleteButton = button "Delete" (DeleteRawModel level) + deleteButton = dangerButton "Delete" (DeleteRawModel level) - params = div [ class "cache-model-params" ] + params = div [] [ labeledInput "Block size" rcm.blockSize (wrapUpdate updateBlockSize) , labeledInput "Set count" rcm.setCount (wrapUpdate updateSetCount) , labeledInput "Set size" rcm.setSize (wrapUpdate updateSetSize) ] in - div [ class "cache-model" ] + panel [ h3 [] [ text <| "L" ++ String.fromInt (level + 1) ++ " Cache" ] , buttonWrapper [ deleteButton ] , params @@ -57,21 +95,15 @@ viewRawCacheModelHierarchy rcmh = <| List.indexedMap viewRawCacheModel rcmh translationResult = Result.andThen validateCacheModelHierarchy <| translateRawCacheModelHierarchy rcmh - isValid = - case translationResult of - Ok _ -> True - Err _ -> False errorHtml = case translationResult of Ok _ -> viewError True "" Err e -> viewError False e newButton = button "Add level" CreateRawModel - useButton = case translationResult of - Ok cmh -> optionalButton True "Use hierarchy" (UseHierarchy <| Just cmh) - Err _ -> optionalButton False "Use hierarchy" (UseHierarchy Nothing) + useButton = resultButton translationResult "Use hierarchy" (UseHierarchy << Just) in - div [ class "cache-model-hierarchy" ] + div [] [ h2 [] [ text "Cache hierarchy" ] , errorHtml , buttonWrapper [ newButton, useButton ] @@ -84,14 +116,14 @@ viewCache level (cm, cs) = slotLabels = List.indexedMap (\i _ -> td [] [ text <| String.fromInt i ]) <| List.repeat cm.setSize () - slotLabel = td [ colspan cm.setSize ] [ text "Slot" ] + slotLabel = th [ colspan cm.setSize ] [ text "Slot" ] allSlotLabels = List.concat <| List.repeat cm.setCount slotLabels allSlotsLabel = List.repeat cm.setCount slotLabel setLabels = List.indexedMap (\i _ -> td [ colspan cm.setSize ] [ text <| String.fromInt i ]) <| List.repeat cm.setCount () - setLabel = [ td [ colspan <| cm.setSize * cm.setCount ] [ text "Set" ] ] + setLabel = [ th [ colspan <| cm.setSize * cm.setCount ] [ text "Set" ] ] setRow set = let slotHtml s = @@ -102,17 +134,17 @@ viewCache level (cm, cs) = List.map slotHtml set cacheRow = List.concat <| List.map setRow cs cacheTable = - table [] - [ tr [ classList [("hidden", cm.setCount == 1)] ] setLabel - , tr [ classList [("hidden", cm.setCount == 1)] ] setLabels - , tr [ classList [("hidden", cm.setSize == 1)] ] allSlotsLabel - , tr [ classList [("hidden", cm.setSize == 1)] ] allSlotLabels + table [ class "table" ] + [ tr [ hidden (cm.setCount == 1), class "table-info" ] setLabel + , tr [ hidden (cm.setCount == 1) ] setLabels + , tr [ hidden (cm.setSize == 1), class "table-info" ] allSlotsLabel + , tr [ hidden (cm.setSize == 1) ] allSlotLabels , tr [] cacheRow ] in - div [ class "cache" ] - [ h3 [] [ text <| "L" ++ String.fromInt level ++ " Cache" ] + panel + [ h3 [] [ text <| "L" ++ String.fromInt (level + 1) ++ " Cache" ] , cacheTable ] @@ -122,7 +154,7 @@ viewCacheHierarchy ch = levels = div [ class "cache-levels" ] <| List.indexedMap viewCache ch in - div [ class "cache-hierarchy" ] <| + div [] <| [ h2 [] [ text <| "Cache State" ] , levels ] @@ -132,11 +164,11 @@ viewAccessView m av = let currentCache = Maybe.withDefault [] m.hierarchy in - div [ class "access-view" ] + div [] [ h2 [] [ text "Access Simulation" ] , buttonWrapper - [ button "Forward" AccessViewForward - , button "Back" AccessViewBack + [ primaryButton "Back" AccessViewBack + , primaryButton "Forward" AccessViewForward ] , viewAccessLog av , viewCacheHierarchy <| effectiveCacheHierarchy currentCache av @@ -145,12 +177,11 @@ viewAccessView m av = viewAccessLog : AccessView -> Html Msg viewAccessLog (aes, ap) = let - resultSpan r = - case r of - Hit -> span [ class "success" ] [ text "HIT" ] - Miss -> span [ class "failure" ] [ text "MISS" ] + resultSpan r = + span [ classList [ ("badge", True), ("badge-danger", True), ("badge-success", r == Hit) ] ] + [ text <| if r == Hit then "Hit" else "Miss" ] downEvent n ae = div [ class "event" ] - [ text <| "L" ++ String.fromInt (n + 1) + [ text <| "L" ++ String.fromInt (n + 1) ++ " " , resultSpan ae.result ] upEvent n ae = div [ class "event" ] @@ -163,35 +194,32 @@ viewAccessLog (aes, ap) = Up n -> List.indexedMap downEvent aes ++ (List.indexedMap upEvent <| List.drop n aes) in - div [ class "access-log" ] + div [ ] [ h3 [] [ text "Simulation events" ] - , div [ class "access-log-events" ] events + , div [] events ] viewAccessInput : Model -> Html Msg viewAccessInput m = let - accessButton = - case String.toInt m.accessInput of - Just i -> optionalButton True "Access address" (Access i) - Nothing -> optionalButton False "Access address" (Access -1) + accessButton = maybeButton (String.toInt m.accessInput) "Access address" Access in - div [ class "access-input" ] + div [] [ h2 [] [ text "Run access simulation" ] - , div [ class "note" ] + , div [ classList [("alert", True), ("alert-primary", True)] ] [ text "Please make sure to click \"Use Hierarchy\" to load a hierarchy to simulate." ] , labeledInput "Access address" m.accessInput ChangeAccessInput , accessButton ] -viewError : Bool -> String -> Html Msg -viewError hide e = div [ classList [ ("hidden", hide), ("error", True) ] ] [ text e ] - viewBase : Model -> Html Msg viewBase m = let - rawView = viewRawCacheModelHierarchy m.rawHierarchy + rawView = + case m.accessView of + Nothing -> [ viewRawCacheModelHierarchy m.rawHierarchy ] + Just _ -> [] cacheView = case m.accessView of Nothing -> @@ -201,4 +229,5 @@ viewBase m = accessView = Maybe.withDefault [] <| Maybe.map (List.singleton << viewAccessView m) <| m.accessView accessInputView = [ viewAccessInput m ] in - div [] <| [ rawView ] ++ cacheView ++ accessView ++ accessInputView + div [ class "container" ] + <| rawView ++ cacheView ++ accessView ++ accessInputView diff --git a/static/scss/style.scss b/static/scss/style.scss index eb25e8a..fcf8bc9 100644 --- a/static/scss/style.scss +++ b/static/scss/style.scss @@ -1,128 +1,3 @@ -@import url('https://fonts.googleapis.com/css?family=Raleway&display=swap'); -$primary-color: lighten(#26c176, 30%); - -body { - font-family: Raleway, serif; -} - -h1, h2, h3, h4, h5, h6 { - margin-bottom: 5px; - margin-top: 5px; -} - -.hidden { - display: none; -} - -.input-group { - width: 100%; - display: flex; - align-items: center; - margin-bottom: 5px; - - span { - margin-right: 10px; - } - - input { - flex-grow: 1; - } -} - -input[type="text"] { - padding: 5px; - border-radius: 5px; - border: none; - background-color: #efefef; - - &:active, &:focus { - background-color: $primary-color; - transition: .25s; - } -} - -.error { - box-sizing: border-box; - padding: 5px; - border-radius: 5px; - margin-bottom: 10px; - border: 2px solid tomato; - color: tomato; -} - -.note { - box-sizing: border-box; - padding: 5px; - border-radius: 5px; - margin-bottom: 10px; -} - -span.success { - margin-left: 10px; - color: green; -} - -span.failure { - margin-left: 10px; - color: tomato; -} - -button, input[type="button"] { - box-sizing: border-box; - border: none; - margin-right: 5px; - margin-bottom: 5px; - display: inline-block; - padding: 5px; - border-radius: 5px; - font-size: 14px; - - &:hover, &:focus { - background-color: $primary-color; - transition: .25s; - - &:disabled { - background-color: lightgrey; - } - } - -} - -.cache-model-levels { - display: flex; - flex-wrap: wrap; -} - -.cache-model { - max-width: 600px; - padding: 10px; - border-radius: 5px; - border: 2px solid lightgrey; - margin: 5px; -} - -.cache, .cache-model { - padding: 10px; - border-radius: 5px; - border: 2px solid lightgrey; - margin: 5px; -} - -.cache-model-hierarchy, .cache-hierarchy, .access-input { - margin-bottom: 10px; -} - -table { - width: 100%; -} - -tr { - &:nth-child(1), &:nth-child(3) { - background-color: $primary-color; - } -} - -td { - text-align: center; - padding: 3px; +.btn-group { + width: min-content; }