mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-25 17:20:01 +08:00
fixed # 更改个人HUD
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Position existing native prompts and popped descriptions; never recreate their content or handlers.
|
||||
export function createOverlayLayout(arena, hand, ui, player) {
|
||||
const records = new Map();
|
||||
const roots = new Set([ui.window, arena].filter(Boolean));
|
||||
const popped = () => new Set([...roots].flatMap(root => [...root.querySelectorAll(':scope > .dialog.popped')]));
|
||||
const markPopups = new WeakSet();
|
||||
let activeMarkPopup = null, openingMark = false, disposed = false, options = null;
|
||||
// Native mark intros do not reject repeated clicks themselves. Observe their real
|
||||
// popup after the native handler, and block duplicate opens without replacing it.
|
||||
const guardMark = event => {
|
||||
const target = event.target.closest?.('.mark');
|
||||
if (!target || !player.contains(target)) return;
|
||||
if (openingMark || activeMarkPopup?.isConnected) {
|
||||
event.preventDefault(); event.stopImmediatePropagation(); return;
|
||||
}
|
||||
const before = popped();
|
||||
openingMark = true;
|
||||
arena.ownerDocument.defaultView.queueMicrotask(() => {
|
||||
openingMark = false;
|
||||
if (disposed) return;
|
||||
activeMarkPopup = [...popped()].find(node => !before.has(node)) || null;
|
||||
if (activeMarkPopup) { markPopups.add(activeMarkPopup); if (options) api.refresh(options); }
|
||||
});
|
||||
};
|
||||
const markEvents = ['click', 'touchend', 'contextmenu'];
|
||||
for (const type of markEvents) player.addEventListener(type, guardMark, true);
|
||||
function release(node, record) {
|
||||
for (const [key, field] of record.styles) {
|
||||
if (node.style.getPropertyValue(key) !== field.last) continue;
|
||||
if (field.before) node.style.setProperty(key,field.before,field.priority); else node.style.removeProperty(key);
|
||||
}
|
||||
for (const [name, had] of record.classes) if (!had) node.classList.remove(name);
|
||||
records.delete(node);
|
||||
}
|
||||
function mark(node, name) {
|
||||
let record=records.get(node);
|
||||
if(!record) {record={styles:new Map(),classes:new Map()};records.set(node,record);}
|
||||
if(!record.classes.has(name))record.classes.set(name,node.classList.contains(name));
|
||||
node.classList.add(name);return record;
|
||||
}
|
||||
function style(node,key,value) {
|
||||
const record=records.get(node);
|
||||
if(!record.styles.has(key))record.styles.set(key,{before:node.style.getPropertyValue(key),priority:node.style.getPropertyPriority(key)});
|
||||
const field=record.styles.get(key);
|
||||
if(field.requested!==value||node.style.getPropertyValue(key)!==field.last)node.style.setProperty(key,value,'important');
|
||||
field.requested=value;field.last=node.style.getPropertyValue(key);
|
||||
}
|
||||
function position(node,x,y,width,maxHeight) {
|
||||
for(const [key,value] of Object.entries({position:'absolute',right:'auto',bottom:'auto',transform:'none',margin:'0px',width:`${width}px`,'max-height':`${maxHeight}px`}))style(node,key,value);
|
||||
const host=node.offsetParent||arena.ownerDocument.documentElement,h=host.getBoundingClientRect();
|
||||
const sx=h.width/host.offsetWidth||1,sy=h.height/host.offsetHeight||sx;
|
||||
style(node,'left',`${(x-h.left)/sx-host.clientLeft+host.scrollLeft}px`);
|
||||
style(node,'top',`${(y-h.top)/sy-host.clientTop+host.scrollTop}px`);
|
||||
}
|
||||
const api = {
|
||||
refresh(nextOptions) {
|
||||
options = nextOptions;
|
||||
const {footerHeight,status} = nextOptions;
|
||||
const used=new Set(),a=arena.getBoundingClientRect(),zoom=a.width/arena.clientWidth||1;
|
||||
const left=parseFloat(arena.style.getPropertyValue('--mengsan-hud-left'))||0;
|
||||
const right=parseFloat(arena.style.getPropertyValue('--mengsan-hud-right'))||0;
|
||||
const x=a.left+left*zoom,width=Math.max(100,arena.clientWidth-left-right);
|
||||
const controlTop=ui.control.getBoundingClientRect().top;
|
||||
const prompts=new Set(arena.querySelectorAll(':scope > .hand-tip,:scope > .hand-tip-info'));
|
||||
const dialog=ui.dialog;
|
||||
if(dialog?.isConnected && !dialog.classList.contains('popped') && !dialog.classList.contains('fullheight') && !dialog.forcebutton && !dialog.buttons?.length && !dialog.querySelector('.button,.card,.player,input,textarea,select') && status.event?.player===player)prompts.add(dialog);
|
||||
// The event owns simple native prompt dialogs too, without a global game dependency.
|
||||
const eventDialog=status.event?.dialog;
|
||||
if(eventDialog?.isConnected && eventDialog===dialog && !eventDialog.buttons?.length && !eventDialog.forcebutton && !eventDialog.querySelector('.button,.card,.player,input,textarea,select'))prompts.add(eventDialog);
|
||||
let promptBottom=Math.min(controlTop-10*zoom,a.bottom-(footerHeight+54)*zoom);
|
||||
for(const node of prompts) {
|
||||
if(node.hidden || node.style.display==='none')continue;
|
||||
mark(node,'mengsan-hud-prompt-shuying');used.add(node);
|
||||
position(node,x,0,width,arena.clientHeight<=500?34:64);
|
||||
const height=node.getBoundingClientRect().height;
|
||||
position(node,x,Math.max(a.top+50*zoom,promptBottom-height),width,arena.clientHeight<=500?34:64);
|
||||
promptBottom=node.getBoundingClientRect().top-6*zoom;
|
||||
}
|
||||
const handBox=hand.getBoundingClientRect();
|
||||
const handTop=Math.min(handBox.top,...[...hand.querySelectorAll('.handcards>.card')].map(node=>node.getBoundingClientRect().top));
|
||||
const popups=popped();
|
||||
let hasPopup=false;
|
||||
for(const node of popups) {
|
||||
const rect=node.getBoundingClientRect();
|
||||
if(!markPopups.has(node) && !records.has(node) && !(rect.bottom>handTop && rect.left<handBox.right && rect.right>handBox.left))continue;
|
||||
mark(node,'mengsan-hud-card-intro-shuying');used.add(node);hasPopup=true;
|
||||
const popupWidth=Math.min(320,Math.max(180,rect.width/zoom),arena.clientWidth-24);
|
||||
const maxHeight=Math.max(64,(promptBottom-a.top)/zoom-12);
|
||||
const center=Math.max(a.left+12*zoom,Math.min(a.right-(popupWidth+12)*zoom,markPopups.has(node)?x:rect.left));
|
||||
position(node,center,a.top+12*zoom,popupWidth,maxHeight);
|
||||
const height=node.getBoundingClientRect().height;
|
||||
position(node,center,Math.max(a.top+12*zoom,promptBottom-height-8*zoom),popupWidth,maxHeight);
|
||||
}
|
||||
if(hasPopup)for(const root of roots)for(const node of root.querySelectorAll(':scope > .poplayer')) {mark(node,'mengsan-hud-intro-layer-shuying');style(node,'z-index','89');used.add(node);}
|
||||
for(const [node,record] of records)if(!node.isConnected||!used.has(node))release(node,record);
|
||||
},
|
||||
dispose(){disposed=true;for(const type of markEvents)player.removeEventListener(type,guardMark,true);for(const [node,record]of records)release(node,record);},
|
||||
};
|
||||
return api;
|
||||
}
|
||||
Reference in New Issue
Block a user