Skip to content

Commit

Permalink
Merge pull request #10874 from DestinyItemManager/energy-meter
Browse files Browse the repository at this point in the history
Reimplement energy meter in CSS
  • Loading branch information
bhollis authored Dec 30, 2024
2 parents a5635fd + c49d2aa commit 40560cd
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 95 deletions.
82 changes: 82 additions & 0 deletions src/app/dim-ui/EnergyIncrements.m.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// <div class=`energyMeterIncrements small`> (or medium)
// <div class=used/><div class=used/><div class=unused/><div class=unavailable/><div class=unavailable/>
// </div>
//
// results:
// ■ ■ ■ □ ▬ ▬

@use '../variables.scss' as *;

.energyMeterIncrements {
--cell-height: 2px;
--cell-margin: 0.5px;
--cell-border: 1px;
composes: flexRow from './common.m.scss';
align-items: center;

&.medium {
--cell-height: 6px;
--cell-margin: 1px;
--cell-border: 3px;
}

// The actual cells
> div {
display: block;
flex-grow: 1;
padding: 0 var(--cell-margin);

&[role='button'] {
// Expand buttons a bit to make them easier to hover and click
padding-top: 3px;
padding-bottom: 3px;
margin-top: -3px;
margin-bottom: -3px;

// Apply the "used" style to all cells up to and including the hovered
// cell - this is a pure-CSS hover preview (we used to use React to do
// this)!
&:is(:has(~ *:hover), :hover)::before {
border-color: white;
height: var(--cell-height);
margin-top: 0;
margin-bottom: 0;
}
// And apply the "unavailable" style to all cells after the hovered cell
&:hover ~ *::before {
border-color: #888;
height: 0;
}
}

// Remove leading/trailing padding for the first/last cell
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}

// The actual boxes you see are these generated elements - this is so we can
// eliminate the gap between items and then put them back with *padding*
// (instead of margin). Padding still triggers hover, so this prevents the
// hover effect from disappearing when you move the mouse between cells.
&::before {
content: '';
display: block;
border: var(--cell-border) solid white;
height: var(--cell-height);
}

&.used::before {
background: white;
}

&.unavailable::before {
border-color: #888;
height: 0;
margin-top: 3px;
margin-bottom: 3px;
}
}
}
10 changes: 10 additions & 0 deletions src/app/dim-ui/EnergyIncrements.m.scss.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 34 additions & 7 deletions src/app/dim-ui/EnergyIncrements.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'app/dim-ui/EnergyMeterIncrements.scss';
import { t } from 'app/i18next-t';
import { DimItem } from 'app/inventory/item-types';
import { EnergySwap } from 'app/loadout-builder/generated-sets/GeneratedSetItem';
import { MAX_ARMOR_ENERGY_CAPACITY } from 'app/search/d2-known-values';
import clsx from 'clsx';
import styles from './EnergyIncrements.m.scss';
import { PressTip } from './PressTip';

// TODO special display for T10 -> T10 + exotic artifice?
Expand All @@ -22,15 +22,42 @@ function EnergyIncrements({
};
}) {
const { energyCapacity, energyUsed } = item?.energy ?? energy!;
return (
<EnergyMeterIncrements
energyCapacity={energyCapacity}
energyUsed={energyUsed}
variant="small"
/>
);
}

export function EnergyMeterIncrements({
energyCapacity,
energyUsed,
minCapacity,
previewUpgrade,
variant,
}: {
energyCapacity: number;
energyUsed: number;
minCapacity?: number;
previewUpgrade?: (i: number) => void;
variant: 'medium' | 'small';
}) {
// layer in possible total slots, then earned slots, then currently used slots
const meterIncrements = Array<string>(MAX_ARMOR_ENERGY_CAPACITY)
.fill('unavailable')
.fill('unused', 0, energyCapacity)
.fill('used', 0, energyUsed);
const meterIncrements = Array<string | undefined>(MAX_ARMOR_ENERGY_CAPACITY)
.fill(styles.unavailable)
.fill(undefined, 0, energyCapacity)
.fill(styles.used, 0, energyUsed);
return (
<div className={clsx('energyMeterIncrements', 'small')}>
<div className={clsx(styles.energyMeterIncrements, { [styles.medium]: variant === 'medium' })}>
{meterIncrements.map((incrementStyle, i) => (
<div key={i} className={incrementStyle} />
<div
key={i}
className={incrementStyle}
role={minCapacity !== undefined && i + 1 > minCapacity ? 'button' : undefined}
onClick={previewUpgrade ? () => previewUpgrade(i + 1) : undefined}
/>
))}
</div>
);
Expand Down
57 changes: 0 additions & 57 deletions src/app/dim-ui/EnergyMeterIncrements.scss

This file was deleted.

6 changes: 1 addition & 5 deletions src/app/item-popup/EnergyMeter.m.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,9 @@
margin: 10px;
}

.clickable {
cursor: pointer;
}

.upgradePreview {
composes: flexRow from '../dim-ui/common.m.scss';
align-items: center;
padding: 4px 0;
overflow: hidden;
box-sizing: border-box;

Expand All @@ -26,6 +21,7 @@
composes: resetButton from '../dim-ui/common.m.scss';
color: var(--theme-text);
font-size: 16px;
margin: 4px 0 0 0;
}

.cost {
Expand Down
1 change: 0 additions & 1 deletion src/app/item-popup/EnergyMeter.m.scss.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 8 additions & 25 deletions src/app/item-popup/EnergyMeter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'app/dim-ui/EnergyMeterIncrements.scss';
import { EnergyMeterIncrements } from 'app/dim-ui/EnergyIncrements';
import { t } from 'app/i18next-t';
import { insertPlug } from 'app/inventory/advanced-write-actions';
import { DimItem } from 'app/inventory/item-types';
Expand All @@ -11,7 +11,6 @@ import { compareBy } from 'app/utils/comparators';
import { errorMessage } from 'app/utils/errors';
import { getFirstSocketByCategoryHash } from 'app/utils/socket-utils';
import Cost from 'app/vendors/Cost';
import clsx from 'clsx';
import { SocketCategoryHashes } from 'data/d2/generated-enums';
import { AnimatePresence, Tween, Variants, motion } from 'motion/react';
import { useState } from 'react';
Expand All @@ -26,7 +25,6 @@ const upgradeAnimateTransition: Tween = { duration: 0.3 };
export default function EnergyMeter({ item }: { item: DimItem }) {
const defs = useD2Definitions()!;
const energyCapacity = item.energy?.energyCapacity || 0;
const [hoverEnergyCapacity, setHoverEnergyCapacity] = useState(0);
const [previewCapacity, setPreviewCapacity] = useState<number>(energyCapacity);
const dispatch = useThunkDispatch();

Expand All @@ -35,15 +33,6 @@ export default function EnergyMeter({ item }: { item: DimItem }) {
}

const minCapacity = item.energy.energyCapacity;

// layer in possible total slots, then earned slots, then currently used slots
const meterIncrements = Array<string>(10)
.fill('unavailable')
.fill('unused', 0, Math.max(minCapacity, hoverEnergyCapacity || previewCapacity || 0))
.fill('used', 0, item.energy.energyUsed);

const handleHoverStart = (i: number) => setHoverEnergyCapacity(i);
const handleHoverEnd = () => setHoverEnergyCapacity(0);
const previewUpgrade = (i: number) => setPreviewCapacity(Math.max(minCapacity, i));
const resetPreview = () => setPreviewCapacity(energyCapacity);

Expand Down Expand Up @@ -81,19 +70,13 @@ export default function EnergyMeter({ item }: { item: DimItem }) {
<div className="item-socket-category-name">
<b>{Math.max(minCapacity, previewCapacity)}</b> <span>{t('EnergyMeter.Energy')}</span>
</div>
<div className={clsx('energyMeterIncrements', 'medium')}>
{meterIncrements.map((incrementStyle, i) => (
<div
key={i}
className={clsx(incrementStyle, {
[styles.clickable]: i + 1 > energyCapacity,
})}
onPointerEnter={() => handleHoverStart(i + 1)}
onPointerLeave={handleHoverEnd}
onClick={() => previewUpgrade(i + 1)}
/>
))}
</div>
<EnergyMeterIncrements
energyCapacity={Math.max(minCapacity, previewCapacity || 0)}
energyUsed={item.energy.energyUsed}
minCapacity={minCapacity}
variant="medium"
previewUpgrade={previewUpgrade}
/>
<AnimatePresence>
{previewCapacity > minCapacity && (
<motion.div
Expand Down

0 comments on commit 40560cd

Please sign in to comment.