Made responsive

This commit is contained in:
Daniel Ledda
2021-07-06 15:01:43 +02:00
parent 97cb0bc550
commit 2d741b9740
20 changed files with 372 additions and 123 deletions

35
.idea/workspace.xml generated
View File

@@ -26,14 +26,25 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="70635ef7-86ab-4681-b98d-dc8e4999995b" name="Default Changelist" comment=""> <list default="true" id="70635ef7-86ab-4681-b98d-dc8e4999995b" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/public/ColorWheel.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/public/resources/favicon.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/ui/ActionButton.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/solve.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solve.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/store.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/store.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/store.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/App.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/App.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/CubeInputSet.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/CubeInputSet.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/ui/CubeInputSet.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/CubeInputSet.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/ExamplesList.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/ExamplesList.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/ui/ExamplesList.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/ExamplesList.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/InputParameters.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/InputParameters.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/List.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/List.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/ui/List.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/List.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/Sidebar.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Sidebar.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/ui/Sidebar.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Sidebar.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/SolutionList.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolutionList.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/ui/SolutionList.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolutionList.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/Tabs.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Tabs.svelte" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/ui/SolutionViewer.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/ThreeDee.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/SolveButton.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolveButton.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/Stage.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Stage.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/threedee/RotationControl.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/RotationControl.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -59,6 +70,7 @@
<option value="SolveButton.svelte" /> <option value="SolveButton.svelte" />
<option value="ExamplesList.svelte" /> <option value="ExamplesList.svelte" />
<option value="List.svelte" /> <option value="List.svelte" />
<option value="ActionButton.svelte" />
</list> </list>
</option> </option>
</component> </component>
@@ -95,6 +107,13 @@
<property name="vue.rearranger.settings.migration" value="true" /> <property name="vue.rearranger.settings.migration" value="true" />
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/public" />
<recent name="$PROJECT_DIR$/public/resources" />
<recent name="$PROJECT_DIR$/public/build" />
<recent name="$PROJECT_DIR$/public/solver" />
<recent name="$PROJECT_DIR$/src/desktop" />
</key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src" /> <recent name="$PROJECT_DIR$/src" />
<recent name="$PROJECT_DIR$/src/ui" /> <recent name="$PROJECT_DIR$/src/ui" />
@@ -102,13 +121,6 @@
<recent name="$PROJECT_DIR$/public" /> <recent name="$PROJECT_DIR$/public" />
<recent name="$PROJECT_DIR$/public/solver" /> <recent name="$PROJECT_DIR$/public/solver" />
</key> </key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/public" />
<recent name="$PROJECT_DIR$/public/build" />
<recent name="$PROJECT_DIR$/public/solver" />
<recent name="$PROJECT_DIR$/src/desktop" />
<recent name="$PROJECT_DIR$/src/solver/js" />
</key>
</component> </component>
<component name="RunManager"> <component name="RunManager">
<configuration name="test.html" type="JavascriptDebugType" temporary="true" nameIsGenerated="true" uri="http://localhost:63343/soma/src/test.html" useBuiltInWebServerPort="true"> <configuration name="test.html" type="JavascriptDebugType" temporary="true" nameIsGenerated="true" uri="http://localhost:63343/soma/src/test.html" useBuiltInWebServerPort="true">
@@ -156,6 +168,7 @@
<workItem from="1625389803555" duration="21540000" /> <workItem from="1625389803555" duration="21540000" />
<workItem from="1625471453862" duration="708000" /> <workItem from="1625471453862" duration="708000" />
<workItem from="1625489120282" duration="24129000" /> <workItem from="1625489120282" duration="24129000" />
<workItem from="1625561814270" duration="14382000" />
</task> </task>
<servers /> <servers />
</component> </component>
@@ -218,10 +231,10 @@
<screen x="0" y="27" width="1920" height="1053" /> <screen x="0" y="27" width="1920" height="1053" />
</state> </state>
<state x="696" y="393" width="518" height="301" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624963157966" /> <state x="696" y="393" width="518" height="301" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624963157966" />
<state x="2012" y="43" width="1720" height="903" key="DiffContextDialog" timestamp="1625505778555"> <state x="92" y="69" width="1720" height="880" key="DiffContextDialog" timestamp="1625575561214">
<screen x="1920" y="0" width="1920" height="1080" /> <screen x="0" y="27" width="1920" height="1053" />
</state> </state>
<state x="92" y="69" width="1720" height="880" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624822125389" /> <state x="92" y="69" width="1720" height="880" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1625575561214" />
<state x="2012" y="43" width="1720" height="903" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@1920.0.1920.1080" timestamp="1625505778555" /> <state x="2012" y="43" width="1720" height="903" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@1920.0.1920.1080" timestamp="1625505778555" />
<state x="743" y="301" width="716" height="623" key="FileChooserDialogImpl" timestamp="1621932819206"> <state x="743" y="301" width="716" height="623" key="FileChooserDialogImpl" timestamp="1621932819206">
<screen x="0" y="27" width="1920" height="1053" /> <screen x="0" y="27" width="1920" height="1053" />

View File

@@ -49,10 +49,7 @@
"linux": { "linux": {
"target": ["AppImage"] "target": ["AppImage"]
}, },
"extraResources": [ "icon": "./public/favicon.png",
"public/solver"
],
"icon": "./public/resources/soma_icon.png",
"directories": { "directories": {
"output": "desktop-dist" "output": "desktop-dist"
} }

BIN
public/ColorWheel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

View File

@@ -13,7 +13,7 @@ import {
totalVolume totalVolume
} from "./store"; } from "./store";
const worker = new Worker('./solver/main.js', {type: 'module'}); const worker = new Worker('./worker.js', {type: 'module'});
async function respondWasm(event: MessageEvent) { async function respondWasm(event: MessageEvent) {
solutions.set(event.data.map((wasmSolution) => { solutions.set(event.data.map((wasmSolution) => {
const solnObj = new SomaSolution(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()); const solnObj = new SomaSolution(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal());

View File

@@ -26,7 +26,14 @@ export const isMinPolycubes = derived(
($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1 ($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1
); );
export const examples = [ type Save = {
name: string,
dimX: number,
dimY: number,
dimZ: number,
cubes: {space: bigint | string, color: string}[],
};
const builtInExamples: Save[] = [
{ {
name: "Standard Soma Cube", name: "Standard Soma Cube",
dimX: 3, dimX: 3,
@@ -114,17 +121,34 @@ export const examples = [
{space: 120n, color: "#0000ff"}, {space: 120n, color: "#0000ff"},
], ],
}, },
].concat(deserealiseSaves()); ];
function deserealiseSaves() { export const examples = writable(builtInExamples.concat(deserealiseSaves()));
function deserealiseSaves(): Save[] {
return localStorage.getItem("saves")?.split("@").map(save => JSON.parse(save)) ?? []; return localStorage.getItem("saves")?.split("@").map(save => JSON.parse(save)) ?? [];
} }
export function serialiseCurrentInput() { function serialiseCurrentInput(): Save {
return { return {
name: "",
dimX: somaDimX.currentVal(), dimX: somaDimX.currentVal(),
dimY: somaDimY.currentVal(), dimY: somaDimY.currentVal(),
dimZ: somaDimZ.currentVal(), dimZ: somaDimZ.currentVal(),
cubes: polycubes.currentVal().map(cube => ({space: cube.getRaw().toString(), color: cube.getColor()})), cubes: polycubes.currentVal().map(cube => ({space: cube.getRaw().toString(), color: cube.getColor()})),
}; };
} }
export function save(name: string) {
const save = serialiseCurrentInput();
save.name = name;
const saveString = JSON.stringify(save);
let oldSaves = localStorage.getItem("saves");
if (oldSaves !== null) {
oldSaves += "@";
} else {
oldSaves = "";
}
localStorage.setItem("saves", oldSaves + saveString);
examples.update(examples => examples.concat(save));
}

View File

@@ -0,0 +1,32 @@
<script lang="ts">
export let onClick: () => void = () => {};
export let tooltip: string | null = null;
export let disabled: boolean = false;
export let text: string;
</script>
<button
on:click={onClick}
title="{tooltip}"
disabled={disabled}>
{text}
</button>
<style>
button {
width: auto;
color: white;
background-color: #ff3e00;
border-radius: 0.5em;
border-style: none;
margin: 0;
cursor: pointer;
}
button:hover {
background-color: #ff6b3e;
}
button:disabled {
color: #999999;
background-color: #a36754;
}
</style>

View File

@@ -10,13 +10,14 @@
<div class="sidebarContainer"> <div class="sidebarContainer">
<Sidebar /> <Sidebar />
</div> </div>
<div class="solutionBodyContainer"> <div class="stage">
<Stage scene="{scene}" /> <Stage scene="{scene}" />
</div> </div>
</main> </main>
<style> <style>
main { main {
position: relative;
display: flex; display: flex;
height: 100%; height: 100%;
margin: 0; margin: 0;
@@ -25,28 +26,41 @@
background-color: black; background-color: black;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 20%; max-width: 20%;
height: 100%; height: 100%;
display: inline-block; display: inline-block;
} }
.solutionBodyContainer { .stage {
flex: 1;
background-color: grey; background-color: grey;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 80%;
height: 100%; height: 100%;
width: 80%;
display: inline-block; display: inline-block;
} }
:global(body) { :global(body) {
color: white; color: white;
background: #333333; background: #333333;
} }
@media(max-width: 1200px) { @media(width: 1200px) {
.solutionBodyContainer {
width: calc(100% - 18em);
}
.sidebarContainer { .sidebarContainer {
width: 18em; width: 18em;
} }
.stage {
width: calc(80% - 18em);
}
}
@media(max-width: 1024px) {
.stage {
left: 2em;
position: absolute;
width: calc(100% - 2em);
}
.sidebarContainer {
position: absolute;
max-width: 100%;
z-index: 1;
}
} }
</style> </style>

View File

@@ -122,7 +122,7 @@
} }
.colorPickerBtn { .colorPickerBtn {
align-self: center; align-self: center;
background-image: url("../resources/ColorWheel.png"); background-image: url("./ColorWheel.png");
background-size: cover; background-size: cover;
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;

View File

@@ -26,9 +26,9 @@
margin: auto; margin: auto;
} }
.container { .container {
flex: 1 1 auto;
overflow-x: scroll; overflow-x: scroll;
display: flex; display: flex;
width: 100%;
flex-flow: row; flex-flow: row;
margin: auto; margin: auto;
} }

View File

@@ -1,67 +1,65 @@
<script lang="ts"> <script lang="ts">
import List from "./List.svelte"; import List from "./List.svelte";
import {polycubes, examples, serialiseCurrentInput} from "../store"; import {polycubes, examples, save} from "../store";
import VoxelSpaceBigInt from "../VoxelSpaceBigInt"; import VoxelSpaceBigInt from "../VoxelSpaceBigInt";
import ActionButton from "./ActionButton.svelte";
const placeholder = "Give your puzzle a name"; const placeholder = "Give your puzzle a name";
let untouchedInput = true; let untouchedInput: boolean = true;
let currentName = placeholder; let currentName: string = placeholder;
let lastClickedExample = 0; let lastClickedExample: number | null = null;
function hydrateExample(exNo: number) { function hydrateExample(exNo: number) {
const example = examples[exNo]; const example = $examples[exNo];
polycubes.setCubes(example.cubes.map((cube, i) => new VoxelSpaceBigInt({ polycubes.setCubes(example.cubes.map((cube, i) => new VoxelSpaceBigInt({
id: i, id: i,
dims: [example.dimX, example.dimY, example.dimZ], dims: [example.dimX, example.dimY, example.dimZ],
space: cube.space, space: BigInt(cube.space),
color: cube.color, color: cube.color,
cullEmpty: false, cullEmpty: false,
}))); })));
lastClickedExample = exNo; lastClickedExample = exNo;
} }
function checkUntouched() {
if (currentName === "") {
untouchedInput = true;
} else {
untouchedInput = false;
}
}
function onInput(e: InputEvent) { function onInput(e: InputEvent) {
currentName = (e.target as HTMLInputElement).value; currentName = (e.target as HTMLInputElement).value;
checkUntouched();
} }
function onFocusText(e: FocusEvent) { function onFocusText(e: FocusEvent) {
if (untouchedInput) { if (untouchedInput) {
currentName = ""; currentName = "";
untouchedInput = false;
} }
} }
function onBlurText(e: FocusEvent) { function onBlurText(e: FocusEvent) {
if (currentName === "") { if (untouchedInput) {
currentName = placeholder; currentName = placeholder;
untouchedInput = true;
} }
} }
function save() {
const save = serialiseCurrentInput();
save["name"] = currentName;
const saveString = JSON.stringify(save);
let oldSaves = window.localStorage.getItem("saves");
if (oldSaves !== null) {
oldSaves += "@";
} else {
oldSaves = "";
}
window.localStorage.setItem("saves", oldSaves + saveString);
}
</script> </script>
<div class="container"> <div class="container">
<List <List
defaultText="No examples found..." defaultText="No examples found..."
items="{examples.map(example => example.name)}" items="{$examples.map(example => example.name)}"
activeItem={lastClickedExample} activeItem={lastClickedExample}
onClick={(i) => hydrateExample(i)} onClick={(i) => hydrateExample(i)}
/> />
</div> </div>
<div class="flex"> <div class="save">
<button on:click={save}>Save as...</button> <ActionButton
onClick={() => save(currentName)}
text={"Save as..."}
disabled={untouchedInput}/>
<input <input
class:untouchedInput class:untouchedInput
value="{currentName}" value="{currentName}"
@@ -75,11 +73,22 @@
.untouchedInput { .untouchedInput {
color: grey; color: grey;
} }
button { .save {
white-space: nowrap; white-space: nowrap;
}
.flex {
display: flex; display: flex;
flex-wrap: wrap;
row-gap: 1em;
align-items: center;
justify-content: space-evenly;
margin-top: 1em;
}
input {
flex-basis: 10em;
flex: 1;
max-width: 100%;
min-width: 50%;
margin: 0 0 0 0.5em;
width: 100%;
} }
.container { .container {
height: 10em; height: 10em;

View File

@@ -32,9 +32,6 @@
downDisabled="{$somaDimZ <= PolycubeStore.MIN_DIMS}" downDisabled="{$somaDimZ <= PolycubeStore.MIN_DIMS}"
down="{() => somaDimZ.set($somaDimZ - 1)}" down="{() => somaDimZ.set($somaDimZ - 1)}"
/> />
{#if $totalVolume > 32}
<p class="warn">The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.</p>
{/if}
</div> </div>
<div class="option"> <div class="option">
@@ -49,7 +46,4 @@
</div> </div>
<style> <style>
.warn {
color: red;
}
</style> </style>

View File

@@ -29,6 +29,10 @@
list-style: none; list-style: none;
height: 2em; height: 2em;
line-height: 2em; line-height: 2em;
white-space: nowrap;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
} }
ul { ul {
position: absolute; position: absolute;

View File

@@ -7,32 +7,46 @@
import ExamplesList from "./ExamplesList.svelte"; import ExamplesList from "./ExamplesList.svelte";
import Tabs from "./Tabs.svelte"; import Tabs from "./Tabs.svelte";
import SolveButton from "./SolveButton.svelte"; import SolveButton from "./SolveButton.svelte";
import ActionButton from "./ActionButton.svelte";
let hidden: boolean = true;
</script> </script>
<div class="container"> <div class="container">
<div class="title"> <div class="controls" class:hidden>
<img class="logo" src="../favicon.png"/><h1>Somaesque</h1> <div class="title">
</div> <img class="logo" src="./resources/favicon.png"/>
<div class="widgets"> <h1>Somaesque</h1>
<div>
<Tabs
selectedTab={"Parameters"}
tabs={{
"Parameters": InputParameters,
"Examples": ExamplesList,
}}/>
</div> </div>
<div> <div class="widgets">
<SolveButton/> <div>
<Tabs
selectedTab={"Parameters"}
tabs={{
"Parameters": InputParameters,
"Examples": ExamplesList,
}}/>
</div>
<div>
<SolveButton/>
</div>
</div>
<h3>Solutions: {$solutions.length}</h3>
<div class="solns">
<SolutionList/>
</div> </div>
</div> </div>
<h3>Solutions: {$solutions.length}</h3> <div class="showHideBtn">
<div class="solns"> <ActionButton text={hidden ? ">" : "<"} onClick={() => {hidden = !hidden}}/>
<SolutionList/>
</div> </div>
</div> </div>
<style> <style>
.container {
display: flex;
height: 100%;
flex-direction: row;
}
.title { .title {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -41,12 +55,16 @@
margin-right: 1em; margin-right: 1em;
display: inline-block; display: inline-block;
height: 3em; height: 3em;
align-self: center;
background-image: url("../resources/favicon.png");
background-size: cover;
width: 3em;
} }
.container { .controls {
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100%;
overflow: hidden; overflow: hidden;
background-color: #333333; background-color: #333333;
padding: 1em; padding: 1em;
@@ -76,4 +94,34 @@
font-size: 3em; font-size: 3em;
font-weight: 100; font-weight: 100;
} }
.showHideBtn {
background-color: #333333;
text-align: center;
line-height: 100%;
width: 2em;
height: 100%;
padding: 0.25em;
flex: 0 0 auto;
display: none;
}
@media(max-width: 1600px) {
h1 {
font-size: 2em;
}
}
@media(max-width: 1024px) {
.controls {
overflow: scroll;
padding-right: 0;
}
.showHideBtn {
display: inline-block;
}
.hidden {
display: none;
}
.container.hidden {
pointer-events: none;
}
}
</style> </style>

View File

@@ -20,5 +20,6 @@
<style> <style>
.container { .container {
height: 100%; height: 100%;
min-height: 10em;
} }
</style> </style>

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {polycubes, solving, somaDimX, somaDimY, somaDimZ, totalVolume} from "../store"; import {polycubes, solving, somaDimX, somaDimY, somaDimZ, totalVolume} from "../store";
import {solve} from "../solve"; import {solve} from "../solve";
import ActionButton from "./ActionButton.svelte";
$: cubes = $polycubes; $: cubes = $polycubes;
let noEmpties: boolean; let noEmpties: boolean;
@@ -26,29 +27,40 @@
} }
</script> </script>
<button <div class="container">
class="solve" <div class="solve">
on:click={solve} <ActionButton
title="{genTooltip(enoughSubcubes, noEmpties, size)}" onClick={solve}
disabled="{$solving || !readyToSolve}"> tooltip={genTooltip(enoughSubcubes, noEmpties, size)}
{$solving ? "Solving..." : "Solve!"} disabled={$solving || !readyToSolve}
</button> text={$solving ? "Solving..." : "Solve!"}/>
</div>
{#if $totalVolume > 32}
<p class="warn">The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.</p>
{/if}
</div>
<style> <style>
button.solve { .container {
width: auto; display: flex;
color: white; flex-direction: row;
background-color: #ff3e00; align-content: center;
font-size: 2em; flex-wrap: wrap;
border-radius: 0.5em; justify-content: space-evenly;
border-style: none; align-items: center;
margin: 0;
cursor: pointer;
} }
button.solve:disabled { .warn {
flex-basis: 7em;
flex: 1;
min-width: 7em;
margin-left: 1em;
color: red;
text-align: left;
}
.solve {
height: min-content;
width: auto; width: auto;
color: #999999;
background-color: #a36754;
font-size: 2em; font-size: 2em;
margin: 0;
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import SolutionViewer from "./SolutionViewer.svelte"; import ThreeDee from "./ThreeDee.svelte";
import CubeInputSet from "./CubeInputSet.svelte"; import CubeInputSet from "./CubeInputSet.svelte";
import PolycubeScene from "./threedee/PolycubeScene"; import PolycubeScene from "./threedee/PolycubeScene";
@@ -12,7 +12,7 @@
} }
const queryListWidth = window.matchMedia("(max-width: 1200px)"); const queryListWidth = window.matchMedia("(max-width: 1200px)");
const queryListHeight = window.matchMedia("(max-height: 920px)"); const queryListHeight = window.matchMedia("(max-height: 900px)");
queryListWidth.addEventListener("change", onMediaChange); queryListWidth.addEventListener("change", onMediaChange);
queryListHeight.addEventListener("change", onMediaChange); queryListHeight.addEventListener("change", onMediaChange);
onMediaChange(); onMediaChange();
@@ -27,11 +27,11 @@
{#if showInput} {#if showInput}
<CubeInputSet/> <CubeInputSet/>
{:else} {:else}
<SolutionViewer scene="{scene}"/> <ThreeDee scene="{scene}"/>
{/if} {/if}
{:else} {:else}
<CubeInputSet/> <CubeInputSet/>
<SolutionViewer scene="{scene}"/> <ThreeDee scene="{scene}"/>
{/if} {/if}
</div> </div>
@@ -59,9 +59,9 @@
border-width: 1px 0 0 0; border-width: 1px 0 0 0;
} }
.viewport { .viewport {
overflow: scroll;
display: flex; display: flex;
height: 100%; height: 100%;
width: 100%;
align-content: center; align-content: center;
justify-content: flex-start; justify-content: flex-start;
flex-direction: column; flex-direction: column;

View File

@@ -3,6 +3,7 @@
import {onMount} from "svelte"; import {onMount} from "svelte";
import {polycubes, solutions, activeSolution, showingSolution} from "../store"; import {polycubes, solutions, activeSolution, showingSolution} from "../store";
import Solution2D from "./Solution2D.svelte"; import Solution2D from "./Solution2D.svelte";
import CubeInput from "./CubeInput.svelte";
export let scene: PolycubeScene; export let scene: PolycubeScene;
const selectedStore = polycubes.selected(); const selectedStore = polycubes.selected();
@@ -10,17 +11,32 @@
$: cube = $polycubes[selectedCube]; $: cube = $polycubes[selectedCube];
$: soln = $solutions[$activeSolution]; $: soln = $solutions[$activeSolution];
let el: HTMLDivElement; let el: HTMLDivElement;
let containerEl: HTMLDivElement;
let loaded: boolean = false; let loaded: boolean = false;
const canvasStyle: Partial<CSSStyleDeclaration> = { const canvasStyle: Partial<CSSStyleDeclaration> = {
borderRadius: "1em", borderRadius: "1em",
}; };
function updateDims() {
if (window.innerWidth < 1200) {
if (containerEl.clientHeight < containerEl.clientWidth * (3 / 4)) {
scene.updateDims({width: (containerEl.clientHeight - 50) * (4 / 3), height: containerEl.clientHeight - 50});
} else {
scene.updateDims({width: containerEl.scrollWidth - 50, height: (containerEl.scrollWidth - 50) * (3 / 4)});
}
} else {
scene.resetDims();
}
}
onMount(() => { onMount(() => {
scene.onLoaded(() => { scene.onLoaded(() => {
scene.mount(el); scene.mount(el);
Object.assign((el.children.item(0) as HTMLElement).style, canvasStyle); Object.assign((el.children.item(0) as HTMLElement).style, canvasStyle);
loaded = true; loaded = true;
window.addEventListener("resize", () => updateDims());
updateDims();
}); });
}); });
@@ -35,11 +51,15 @@
} }
</script> </script>
<div class="container"> <div class="container" bind:this={containerEl}>
{#if $activeSolution !== null} {#if $showingSolution && ($activeSolution !== null)}
<div class="soln2d-container"> <div class="soln2d-container">
<Solution2D/> <Solution2D/>
</div> </div>
{:else if !$showingSolution}
<div class="soln2d-container">
<CubeInput cubeNo="{selectedCube}"/>
</div>
{/if} {/if}
<div class="stage" bind:this={el}></div> <div class="stage" bind:this={el}></div>
</div> </div>
@@ -49,6 +69,11 @@
flex: 0 1 auto; flex: 0 1 auto;
display: inline-block; display: inline-block;
} }
@media (max-width: 1200px) {
.soln2d-container {
display: none;
}
}
.container { .container {
flex: 1 1 auto; flex: 1 1 auto;
display: flex; display: flex;
@@ -57,5 +82,6 @@
justify-content: space-evenly; justify-content: space-evenly;
text-align: center; text-align: center;
align-items: center; align-items: center;
overflow: hidden;
} }
</style> </style>

View File

@@ -21,8 +21,10 @@ export default class PolycubeScene {
private canvas: HTMLCanvasElement; private canvas: HTMLCanvasElement;
private loadedCb: () => void = () => {}; private loadedCb: () => void = () => {};
private loaded: boolean = false; private loaded: boolean = false;
private windowDims: {width: number, height: number};
constructor() { constructor(windowDims?: {width: number, height: number}) {
this.windowDims = windowDims ?? {width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT};
this.init().then(() => this.loadedCb()).catch(e => console.log(e)); this.init().then(() => this.loadedCb()).catch(e => console.log(e));
} }
@@ -56,12 +58,24 @@ export default class PolycubeScene {
} }
mount(el: HTMLDivElement) { mount(el: HTMLDivElement) {
this.canvas.width = DEFAULT_WIDTH; this.updateDims(this.windowDims);
this.canvas.height = DEFAULT_HEIGHT; el.append(this.canvas);
}
updateDims(windowDims: {width: number, height: number}) {
this.windowDims.width = windowDims.width;
this.windowDims.height = windowDims.height;
this.canvas.width = this.windowDims.width;
this.canvas.height = this.windowDims.height;
this.camera.aspect = this.canvas.width / this.canvas.height; this.camera.aspect = this.canvas.width / this.canvas.height;
this.camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
this.renderer.setSize(this.canvas.width, this.canvas.height); this.renderer.setSize(this.canvas.width, this.canvas.height);
el.append(this.canvas); }
resetDims() {
this.windowDims.width = DEFAULT_WIDTH;
this.windowDims.height = DEFAULT_HEIGHT;
this.updateDims(this.windowDims);
} }
onLoaded(cb: () => void) { onLoaded(cb: () => void) {

View File

@@ -1,4 +1,5 @@
import type * as THREE from 'three'; import type * as THREE from 'three';
import {MOUSE} from "three";
interface Fliable { interface Fliable {
flyBy(factor: number); flyBy(factor: number);
@@ -17,6 +18,10 @@ export default class RotationControls {
private start: THREE.Euler; private start: THREE.Euler;
private fliables: Fliable[]; private fliables: Fliable[];
private hovered: boolean = false; private hovered: boolean = false;
private scrolling: boolean = false;
private lastTouch: {x: number, y: number} = {x: 0, y: 0};
private lastScroll1: {x: number, y: number} = {x: 0, y: 0};
private lastScroll2: {x: number, y: number} = {x: 0, y: 0};
constructor(object: THREE.Object3D, fliables: Fliable[], camera: THREE.Camera, element: HTMLCanvasElement) { constructor(object: THREE.Object3D, fliables: Fliable[], camera: THREE.Camera, element: HTMLCanvasElement) {
this.object = object; this.object = object;
@@ -28,23 +33,29 @@ export default class RotationControls {
this.xAxis = this.xAxis.clone().cross(this.yAxis.clone()); this.xAxis = this.xAxis.clone().cross(this.yAxis.clone());
this.start = this.object.rotation.clone(); this.start = this.object.rotation.clone();
this.element.addEventListener('touchstart', (ev) => this.handleTouchStart(ev));
this.element.addEventListener("touchcancel", (ev) => this.handleTouchEnd(ev));
window.addEventListener('touchmove', (ev) => this.handleTouchMove(ev));
window.addEventListener('touchend', (ev) => this.handleTouchEnd(ev));
this.element.addEventListener('wheel', (ev) => this.handleScroll(ev));
this.element.addEventListener('mouseover', () => this.hovered = true); this.element.addEventListener('mouseover', () => this.hovered = true);
this.element.addEventListener('mouseout', () => this.hovered = false); this.element.addEventListener('mouseout', () => this.hovered = false);
this.element.addEventListener('wheel', (ev) => this.handleScroll(ev)); this.element.addEventListener('mousedown', (ev) => this.handleMouseDown(ev));
this.element.addEventListener('mousedown', (event) => {
if (event.button === 1) {
this.object.setRotationFromEuler(this.start);
}
if (!this.dragging) {
this.lastX = event.x;
this.lastY = event.y;
this.dragging = true;
}
});
window.addEventListener('mousemove', (ev) => this.handleMove(ev)); window.addEventListener('mousemove', (ev) => this.handleMove(ev));
window.addEventListener('mouseup', () => this.dragging = false); window.addEventListener('mouseup', () => this.dragging = false);
} }
private handleMouseDown(event: MouseEvent) {
if (event.button === 1) {
this.object.setRotationFromEuler(this.start);
}
if (!this.dragging) {
this.lastX = event.x;
this.lastY = event.y;
this.dragging = true;
}
}
private handleMove(event: MouseEvent) { private handleMove(event: MouseEvent) {
if (this.dragging) { if (this.dragging) {
const xDiff = event.movementX * RotationControls.ROTATION_FACTOR; const xDiff = event.movementX * RotationControls.ROTATION_FACTOR;
@@ -63,6 +74,56 @@ export default class RotationControls {
} }
} }
private handleTouchMove(event: TouchEvent) {
if (this.dragging) {
const newTouchX = event.touches.item(0).clientX;
const newTouchY = event.touches.item(0).clientY;
const touchDiffX = newTouchX - this.lastTouch.x;
const touchDiffY = newTouchY - this.lastTouch.y;
const xDiff = touchDiffX * RotationControls.ROTATION_FACTOR;
const yDiff = touchDiffY * RotationControls.ROTATION_FACTOR;
this.object.rotateOnAxis(this.yAxis, xDiff);
this.object.rotateOnWorldAxis(this.xAxis, yDiff);
this.lastTouch.x = newTouchX;
this.lastTouch.y = newTouchY;
} else if (this.scrolling) {
if (this.flyingEnabled && this.hovered) {
const newTouchX1 = event.touches.item(0).clientX;
const newTouchX2 = event.touches.item(1).clientX;
const newTouchY1 = event.touches.item(0).clientY;
const newTouchY2 = event.touches.item(1).clientY;
const lastDist = Math.sqrt((this.lastScroll1.x - this.lastScroll2.x) ** 2 + (this.lastScroll1.y - this.lastScroll2.y) ** 2);
const newDist = Math.sqrt((newTouchX1 - newTouchX2) ** 2 + (newTouchY1 - newTouchY2) ** 2)
const delta = newDist - lastDist;
for (const fliable of this.fliables) {
const direction = delta / Math.abs(delta);
fliable.flyBy(direction / 10);
}
}
}
}
private handleTouchStart(event: TouchEvent) {
if (event.touches.length === 1) {
this.lastTouch.x = event.touches.item(0).clientX;
this.lastTouch.y = event.touches.item(0).clientY;
this.dragging = true;
} else if (event.touches.length === 2) {
this.lastScroll1.x = event.touches.item(0).clientX;
this.lastScroll1.y = event.touches.item(0).clientY;
this.lastScroll2.x = event.touches.item(1).clientX;
this.lastScroll2.y = event.touches.item(1).clientY;
this.scrolling = true;
}
this.hovered = true;
}
private handleTouchEnd(event: TouchEvent) {
this.dragging = false;
this.scrolling = false;
this.hovered = false;
}
enableFly() { enableFly() {
this.flyingEnabled = true; this.flyingEnabled = true;
} }