From 254071a575cc8fae525d8eac953df5134f0b78b4 Mon Sep 17 00:00:00 2001 From: onsetsu Date: Wed, 20 Dec 2023 15:48:48 +0100 Subject: [PATCH 1/3] added discover keyword to ubg SQUASHED: AUTO-COMMIT-src-components-widgets-ubg-cards.js, --- src/components/widgets/ubg-cards.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/widgets/ubg-cards.js b/src/components/widgets/ubg-cards.js index f5ac00032..691bcb745 100644 --- a/src/components/widgets/ubg-cards.js +++ b/src/components/widgets/ubg-cards.js @@ -532,6 +532,10 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return `To upgrade, trash ${whoText} to gain a card costing up to (${diff}) more.` }, + discover: (howMany) => { + return `To discover ${howMany}, reveal top ${howMany} cards of any piles. Add 1 to your hand, trash the rest.` + }, + evoke: (cost) => { return `As a free action, pay (${cost}) and trash this from hand to exec its blitz effects.` }, From e027a701f01edfc36bd87313605c64be67477c09 Mon Sep 17 00:00:00 2001 From: Stefan Ramson Date: Thu, 21 Dec 2023 17:54:51 +0100 Subject: [PATCH 2/3] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5dc758987..c0878a928 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A project or branch of a project on GitHub can be checked out in multiple direct - [lively4-core](https://lively-kernel.org/lively4/lively4-core/start.html) - [lively4-stable](https://lively-kernel.org/lively4/lively4-stable/start.html) - [lively4-jens](https://lively-kernel.org/lively4/lively4-jens/start.html) +- [aexpr](https://lively-kernel.org/lively4/aexpr/start.html) The Lively4 server and GitHub sync tools can check out arbitrary projects, such as the code of [lively4-server](https://lively-kernel.org/lively4/lively4-server/) itself, or the source of a paper hosted by overleaf. @@ -35,4 +36,4 @@ The Lively4 server and GitHub sync tools can check out arbitrary projects, such - Reactive Programming [RP 2018](https://lively-kernel.org/lively4/lively4-seminars/RP2018/index.md) -### [Imprint](imprint.md) \ No newline at end of file +### [Imprint](imprint.md) From 31d22332169a05ac816d468917f536d16c9f65b5 Mon Sep 17 00:00:00 2001 From: onsetsu Date: Sun, 31 Dec 2023 15:11:43 +0100 Subject: [PATCH 3/3] webxr tests SQUASHED: AUTO-COMMIT-demos-stefan-untitled-board-game-ubg-card.js,AUTO-COMMIT-demos-stefan-webxr.md,AUTO-COMMIT-demos-stefan-webxr-README.md,AUTO-COMMIT-demos-stefan-webxr-webxr.js,AUTO-COMMIT-demos-stefan-webxr-webxr.md,AUTO-COMMIT-src-components-widgets-ubg-cards-editor.html,AUTO-COMMIT-src-components-widgets-ubg-cards-editor.js,AUTO-COMMIT-src-components-widgets-ubg-cards-entry.html,AUTO-COMMIT-src-components-widgets-ubg-cards-entry.js,AUTO-COMMIT-src-components-widgets-ubg-cards.js, --- demos/stefan/untitled-board-game/ubg-card.js | 17 +- demos/stefan/webxr/README.md | 28 +++ demos/stefan/webxr/webxr.js | 173 +++++++++++++++++++ src/components/widgets/ubg-cards-editor.html | 22 ++- src/components/widgets/ubg-cards-editor.js | 35 +++- src/components/widgets/ubg-cards-entry.js | 8 +- src/components/widgets/ubg-cards.js | 83 ++++++++- 7 files changed, 353 insertions(+), 13 deletions(-) create mode 100644 demos/stefan/webxr/README.md create mode 100644 demos/stefan/webxr/webxr.js diff --git a/demos/stefan/untitled-board-game/ubg-card.js b/demos/stefan/untitled-board-game/ubg-card.js index ab5a8bed3..645ae396f 100644 --- a/demos/stefan/untitled-board-game/ubg-card.js +++ b/demos/stefan/untitled-board-game/ubg-card.js @@ -90,10 +90,23 @@ export default class Card { this.ensureUnprintedVersion(); if (notes === undefined) { - delete this.versions.last.notes; + delete this.notes; } else { this.notes = notes; - this.versions.last.notes = notes; + } + } + + getRating() { + return this.versions.last.rating; + } + + setRating(rating) { + this.ensureUnprintedVersion(); + + if (rating === undefined || rating === 'unset') { + delete this.versions.last.rating; + } else { + this.versions.last.rating = rating; } } diff --git a/demos/stefan/webxr/README.md b/demos/stefan/webxr/README.md new file mode 100644 index 000000000..3f0e0f681 --- /dev/null +++ b/demos/stefan/webxr/README.md @@ -0,0 +1,28 @@ + + + + + + + + +# Immersive VR Session + +
+
+ Immersive VR Session +

+ This sample demonstrates how to use an 'immersive-vr' XRSession to + present a simple WebGL scene to an XR device. The scene is not + rendered to the page. + Back +

+
+
+
+

Click 'Enter XR' to see content

+
+ diff --git a/demos/stefan/webxr/webxr.js b/demos/stefan/webxr/webxr.js new file mode 100644 index 000000000..05bd91ec0 --- /dev/null +++ b/demos/stefan/webxr/webxr.js @@ -0,0 +1,173 @@ +import {WebXRButton} from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/util/webxr-button.js'; +import {Scene} from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/render/scenes/scene.js'; +import {Renderer, createWebGLContext} from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/render/core/renderer.js'; +import {Gltf2Node} from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/render/nodes/gltf2.js'; +import {SkyboxNode} from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/render/nodes/skybox.js'; +import {QueryArgs} from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/util/query-args.js'; + +// If requested, use the polyfill to provide support for mobile devices +// and devices which only support WebVR. +import WebXRPolyfill from 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/js/third-party/webxr-polyfill/build/webxr-polyfill.module.js'; +if (QueryArgs.getBool('usePolyfill', true)) { + let polyfill = new WebXRPolyfill(); +} + +// XR globals. +let xrButton = null; +let xrRefSpace = null; + +// WebGL scene globals. +let gl = null; +let renderer = null; +let scene = new Scene(); +scene.addNode(new Gltf2Node({url: 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/media/gltf/space/space.gltf'})); +scene.addNode(new SkyboxNode({url: 'https://raw.githubusercontent.com/immersive-web/webxr-samples/main/media/textures/milky-way-4k.png'})); + +// Checks to see if WebXR is available and, if so, queries a list of +// XRDevices that are connected to the system. +export default function initXR(context) { + // Adds a helper button to the page that indicates if any XRDevices are + // available and let's the user pick between them if there's multiple. + xrButton = new WebXRButton({ + onRequestSession: onRequestSession, + onEndSession: onEndSession + }); + lively.query(context, 'header').appendChild(xrButton.domElement); + + // Is WebXR available on this UA? + if (navigator.xr) { + // If the device allows creation of exclusive sessions set it as the + // target of the 'Enter XR' button. + navigator.xr.isSessionSupported('immersive-vr').then((supported) => { + xrButton.enabled = supported; + }); + } +} + +// Called when the user selects a device to present to. In response we +// will request an exclusive session from that device. +function onRequestSession() { + return navigator.xr.requestSession('immersive-vr').then(onSessionStarted); +} + +// Called when we've successfully acquired a XRSession. In response we +// will set up the necessary session state and kick off the frame loop. +function onSessionStarted(session) { + // This informs the 'Enter XR' button that the session has started and + // that it should display 'Exit XR' instead. + xrButton.setSession(session); + + // Listen for the sessions 'end' event so we can respond if the user + // or UA ends the session for any reason. + session.addEventListener('end', onSessionEnded); + + // Create a WebGL context to render with, initialized to be compatible + // with the XRDisplay we're presenting to. + gl = createWebGLContext({ + xrCompatible: true + }); + + // Create a renderer with that GL context (this is just for the samples + // framework and has nothing to do with WebXR specifically.) + renderer = new Renderer(gl); + + // Set the scene's renderer, which creates the necessary GPU resources. + scene.setRenderer(renderer); + + // Use the new WebGL context to create a XRWebGLLayer and set it as the + // sessions baseLayer. This allows any content rendered to the layer to + // be displayed on the XRDevice. + session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) }); + + // Get a frame of reference, which is required for querying poses. In + // this case an 'local' frame of reference means that all poses will + // be relative to the location where the XRDevice was first detected. + session.requestReferenceSpace('local').then((refSpace) => { + xrRefSpace = refSpace; + + // Inform the session that we're ready to begin drawing. + session.requestAnimationFrame(onXRFrame); + }); +} + +// Called when the user clicks the 'Exit XR' button. In response we end +// the session. +function onEndSession(session) { + session.end(); +} + +// Called either when the user has explicitly ended the session (like in +// onEndSession()) or when the UA has ended the session for any reason. +// At this point the session object is no longer usable and should be +// discarded. +function onSessionEnded(event) { + xrButton.setSession(null); + + // In this simple case discard the WebGL context too, since we're not + // rendering anything else to the screen with it. + renderer = null; +} + +// Called every time the XRSession requests that a new frame be drawn. +function onXRFrame(t, frame) { + let session = frame.session; + + // Per-frame scene setup. Nothing WebXR specific here. + scene.startFrame(); + + // Inform the session that we're ready for the next frame. + session.requestAnimationFrame(onXRFrame); + + // Get the XRDevice pose relative to the Frame of Reference we created + // earlier. + let pose = frame.getViewerPose(xrRefSpace); + + // Getting the pose may fail if, for example, tracking is lost. So we + // have to check to make sure that we got a valid pose before attempting + // to render with it. If not in this case we'll just leave the + // framebuffer cleared, so tracking loss means the scene will simply + // disappear. + if (pose) { + let glLayer = session.renderState.baseLayer; + + // If we do have a valid pose, bind the WebGL layer's framebuffer, + // which is where any content to be displayed on the XRDevice must be + // rendered. + gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer); + + // Clear the framebuffer + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Loop through each of the views reported by the frame and draw them + // into the corresponding viewport. + for (let view of pose.views) { + let viewport = glLayer.getViewport(view); + gl.viewport(viewport.x, viewport.y, + viewport.width, viewport.height); + + // Draw this view of the scene. What happens in this function really + // isn't all that important. What is important is that it renders + // into the XRWebGLLayer's framebuffer, using the viewport into that + // framebuffer reported by the current view, and using the + // projection matrix and view transform from the current view. + // We bound the framebuffer and viewport up above, and are passing + // in the appropriate matrices here to be used when rendering. + scene.draw(view.projectionMatrix, view.transform); + } + } else { + // There's several options for handling cases where no pose is given. + // The simplest, which these samples opt for, is to simply not draw + // anything. That way the device will continue to show the last frame + // drawn, possibly even with reprojection. Alternately you could + // re-draw the scene again with the last known good pose (which is now + // likely to be wrong), clear to black, or draw a head-locked message + // for the user indicating that they should try to get back to an area + // with better tracking. In all cases it's possible that the device + // may override what is drawn here to show the user it's own error + // message, so it should not be anything critical to the application's + // use. + } + + // Per-frame scene teardown. Nothing WebXR specific here. + scene.endFrame(); +} diff --git a/src/components/widgets/ubg-cards-editor.html b/src/components/widgets/ubg-cards-editor.html index 9cd7e3294..136d8dc34 100644 --- a/src/components/widgets/ubg-cards-editor.html +++ b/src/components/widgets/ubg-cards-editor.html @@ -128,7 +128,7 @@ #form-layout { display: grid; grid-template-columns: min-content auto 2.5in; - grid-template-rows: repeat(7, auto) 1fr auto .5fr auto; + grid-template-rows: repeat(7, auto) 1fr auto auto .5fr auto; grid-template-areas: "isPrinted-key isPrinted-value preview" "id-key id-value preview" @@ -139,6 +139,7 @@ "vp-key vp-value preview" "text-key text-value preview" "tags-key tags-value preview" + "rating-key rating-value preview" "notes-key notes-value preview" "art-key art-value preview" ; @@ -217,6 +218,11 @@ .tag:focus { background: #32cd3280; } + #rating { + height: + } + +
is printed @@ -235,14 +241,26 @@ text + tags + rating +
+ + + + + + + + +
notes art - tags +
diff --git a/src/components/widgets/ubg-cards-editor.js b/src/components/widgets/ubg-cards-editor.js index 47500a362..e8a356521 100644 --- a/src/components/widgets/ubg-cards-editor.js +++ b/src/components/widgets/ubg-cards-editor.js @@ -30,7 +30,15 @@ export default class UBGCardsEditor extends Morph { } this.$text.addEventListener('keydown', evt => this.keydown$text(evt), false); this.$tagsInput.addEventListener('keydown', evt => this.keydown$tagInput(evt), false); - + this.get('#rating').addEventListener('change', evt => { + if (evt.target.name === 'rating') { + this.modify$rating(evt) + } + }); + } + + initSlider() { + } get ubg() { @@ -227,6 +235,9 @@ export default class UBGCardsEditor extends Morph { get $tagsList() { return this.get('#tags-list'); } + get $rating() { + return this.get('#rating'); + } get $notes() { return this.get('#notes'); } @@ -497,6 +508,27 @@ export default class UBGCardsEditor extends Morph { } } + modify$rating(evt) { + const rating = evt.target.value; + if (rating === '') { + this.card.setRating(); + } else { + this.card.setRating(rating); + } + + this.propagateChange() + } + display$rating() { + const rating = this.card.getRating() || 'unset'; + + const selectedOption = this.$rating.querySelector(`[value='${rating}']`) + if (selectedOption) { + selectedOption.checked = true; + } else { + lively.warn('Unknown rating ' + rating) + } + } + modify$notes(evt) { const notes = this.$notes.value; if (notes === '') { @@ -570,6 +602,7 @@ export default class UBGCardsEditor extends Morph { this.display$vp(); this.display$text(); this.display$tags(); + this.display$rating(); this.display$notes(); this.display$art(); this.display$isPrinted(); diff --git a/src/components/widgets/ubg-cards-entry.js b/src/components/widgets/ubg-cards-entry.js index 8d84c85f3..011fa7e9c 100644 --- a/src/components/widgets/ubg-cards-entry.js +++ b/src/components/widgets/ubg-cards-entry.js @@ -116,7 +116,13 @@ export default class UBGCardEntry extends Morph { const v = card.versions.last; - this.get('#id').innerHTML = card.id || '???'; + const id = this.get('#id') + id.style.borderLeft = '5px solid ' + ({ + keep: 'green', + unsure: 'yellow', + remove: 'red', + }[card.getRating()] || 'gray'); + id.innerHTML = card.id || '???'; const type = v.type && v.type.toLowerCase(); this.get('#type').className = { diff --git a/src/components/widgets/ubg-cards.js b/src/components/widgets/ubg-cards.js index 691bcb745..eb4fc9289 100644 --- a/src/components/widgets/ubg-cards.js +++ b/src/components/widgets/ubg-cards.js @@ -235,6 +235,52 @@ const castIcon = do { ${mainElements}`, bounds); } + +const hedronSVG = do { + function point(pt) { + return `${pt.x} ${pt.y}`; + } + + const topB = lively.pt(11.5, 14.401); + const topL = topB.addXY(-11.5, -4.758); + const topT = topL.addXY(11.5, -9.66); + const topR = topT.addXY(11.5, 9.66); + const topB2 = topR.addXY(-11.5, 4.758); + const topLeftData = `M${point(topB)} L ${point(topL)} ${point(topT)} z`; + const topRightData = `M${point(topB)} L ${point(topT)} ${point(topR)} z`; + + const bottomB = lively.pt(11.5, 16.036); + const bottomL = bottomB.addXY(-11.5, -5.050); + const bottomT = bottomL.addXY(11.5, 12.030); + const bottomR = bottomT.addXY(11.5, -12.030); + const bottomB2 = bottomR.addXY(-11.5, 5.050); + const bottomLeftData = `M${point(bottomB)} L ${point(bottomL)} ${point(bottomT)} z`; + const bottomRightData = `M${point(bottomB)} L ${point(bottomT)} ${point(bottomR)} ${point(bottomB2)} z`; + + + + + + + ; +}; + +{ + const hedronTemp = document.getElementById('hedron') + if (hedronTemp) { + hedronTemp.remove() + } + document.body.insertAdjacentHTML("afterbegin", hedronSVG.outerHTML) +} + + class FileCache { constructor() { @@ -364,12 +410,15 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 printedRules = printedRules.replace(/manaCost(fire|water|earth|wind|gray)/gmi, (match, pElement, offset, string, groups) => { return this.manaCost(pElement); }); + printedRules = this.renderElementIcon(printedRules) printedRules = this.renderVPIcon(printedRules) printedRules = this.renderCoinIcon(printedRules) printedRules = this.renderBracketIcon(printedRules) + printedRules = this.renderHedronIcon(printedRules) + return this.renderToDoc(ruleBox, insetTextBy, printedRules, beforeRenderRules, doc) } @@ -439,7 +488,7 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 if (elements.length === 0 || (elements.length === 1 && elements.first === 'gray')) { elementString = 'this card\'s element'; } else if (elements.length === 1) { - elementString = elements.first; + elementString = elements.first; } else { elementString = `${elements.slice(0, -1).join(', ')} or ${elements.last}`; } @@ -516,11 +565,16 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return 'To cycle a card, trash it to gain a card of equal or lower cost.' }, - cycling: (cost) => { + cycling: (cost, who) => { + let whoToPrint = 'this' + if (who === 'acard') { + whoToPrint = 'a card' + } + if (cost) { - return `Passive As a free action, you may pay (${cost}) and trash this to gain a card of equal or lower cost.` + return `Passive As a free action, you may pay (${cost}) and trash ${whoToPrint} to gain a card of equal or lower cost.` } - return `Passive As a free action, you may trash this to gain a card of equal or lower cost.` + return `Passive As a free action, you may trash ${whoToPrint} to gain a card of equal or lower cost.` }, upgrade: (diff, who) => { @@ -561,6 +615,14 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return printedRules.replace(/(fire|water|earth|wind|gray)/gmi, (match, pElement, offset, string, groups) => inlineElement(pElement)); } + static renderHedronIcon(printedRules) { + function inlineHedron() { + return SVG.inlineSVG(hedronSVG.innerHTML, lively.rect(0, 0, 23, 23), 'x="10%" y="10%" width="80%" height="80%"', '') + } + + return printedRules.replace(/hedron/gmi, (match, pElement, offset, string, groups) => inlineHedron()); + } + static renderVPIcon(printedRules) { function printVP(vp) { @@ -579,12 +641,16 @@ ${SVG.inlineSVG(`${text}`; + if (text.includes('hedron')) { + textToPrint = text + } return SVG.inlineSVG(`${SVG.circle(center, 5, `fill="goldenrod"`)} ${SVG.circleRing(center, 4.75, 5, `fill="darkviolet"`)} -${text}`); +${textToPrint}`); } - return printedRules.replace(/\(([*0-9xy+-]*)\)/gmi, function replacer(match, p1, offset, string, groups) { + return printedRules.replace(/\(((?:[*0-9xy+-]|hedron)*)\)/gmi, function replacer(match, p1, offset, string, groups) { return coin(p1); }); } @@ -595,7 +661,7 @@ ${SVG.circleRing(center, 4.75, 5, `fill="darkviolet"`)} return SVG.inlineSVG(` -${text}`); +${text}`, undefined, undefined, 'transform:scale(1);'); } return printedRules.replace(/\[([*0-9x+-]*)\]/gmi, function replacer(match, p1, offset, string, groups) { @@ -1868,6 +1934,9 @@ export default class Cards extends Morph { if (cardDesc.hasTag('deprecated')) { slash('#ff00ff', 2, lively.pt(2, 2)) } + if (cardDesc.getRating() === 'remove') { + slash('#999999', 5, lively.pt(-5, -5)) + } } renderVersionIndicator(doc, cardDesc, outsideBorder) {