Update app_enhanced.py
Browse files- app_enhanced.py +73 -36
app_enhanced.py
CHANGED
|
@@ -543,14 +543,14 @@ class EnhancedComicGenerator:
|
|
| 543 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 544 |
.speech-bubble textarea { position: absolute; top: 0; left: 0; width: 100%; height: 100%; box-sizing: border-box; border: 1px solid #4CAF50; background: rgba(255,255,255,0.95); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
|
| 545 |
|
| 546 |
-
/* <<< MODIFICATION START: New CSS
|
| 547 |
.speech-bubble.speech {
|
| 548 |
background: var(--bubble-fill-color, white);
|
| 549 |
color: var(--bubble-text-color, #333);
|
| 550 |
border-radius: .4em;
|
| 551 |
padding: 0;
|
| 552 |
border: none;
|
| 553 |
-
box-shadow: 0
|
| 554 |
}
|
| 555 |
|
| 556 |
.speech-bubble.speech::after {
|
|
@@ -558,47 +558,43 @@ class EnhancedComicGenerator:
|
|
| 558 |
position: absolute;
|
| 559 |
width: 0;
|
| 560 |
height: 0;
|
| 561 |
-
/* The color is inherited from the parent's fill color */
|
| 562 |
border-color: transparent;
|
| 563 |
border-style: solid;
|
|
|
|
|
|
|
| 564 |
}
|
| 565 |
|
| 566 |
-
/* 4-WAY TAIL ROTATION CLASSES using the CSS Triangle method */
|
| 567 |
.speech-bubble.speech.tail-bottom::after {
|
| 568 |
bottom: 0;
|
| 569 |
-
left: 50
|
| 570 |
-
border-width: 20px;
|
| 571 |
border-top-color: var(--bubble-fill-color, white);
|
| 572 |
border-bottom: 0;
|
| 573 |
-
|
| 574 |
-
margin-bottom: -20px;
|
| 575 |
}
|
| 576 |
.speech-bubble.speech.tail-top::after {
|
| 577 |
top: 0;
|
| 578 |
-
left: 50
|
| 579 |
-
border-width: 20px;
|
| 580 |
border-bottom-color: var(--bubble-fill-color, white);
|
| 581 |
border-top: 0;
|
| 582 |
-
|
| 583 |
-
margin-top: -20px;
|
| 584 |
}
|
| 585 |
.speech-bubble.speech.tail-right::after {
|
| 586 |
-
top: 50
|
| 587 |
right: 0;
|
| 588 |
-
border-width: 20px;
|
| 589 |
border-left-color: var(--bubble-fill-color, white);
|
| 590 |
border-right: 0;
|
| 591 |
-
|
| 592 |
-
margin-right: -20px;
|
| 593 |
}
|
| 594 |
.speech-bubble.speech.tail-left::after {
|
| 595 |
-
top: 50
|
| 596 |
left: 0;
|
| 597 |
-
border-width: 20px;
|
| 598 |
border-right-color: var(--bubble-fill-color, white);
|
| 599 |
border-left: 0;
|
| 600 |
-
|
| 601 |
-
margin-left: -20px;
|
| 602 |
}
|
| 603 |
/* <<< MODIFICATION END >>> */
|
| 604 |
|
|
@@ -632,6 +628,8 @@ class EnhancedComicGenerator:
|
|
| 632 |
.color-picker-grid div { text-align: center; }
|
| 633 |
.color-picker-grid label { font-size: 11px; }
|
| 634 |
.color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
|
|
|
|
|
|
|
| 635 |
</style>
|
| 636 |
</head>
|
| 637 |
<body>
|
|
@@ -663,10 +661,22 @@ class EnhancedComicGenerator:
|
|
| 663 |
<input type="color" id="bubble-fill-color" value="#FFFFFF" disabled>
|
| 664 |
</div>
|
| 665 |
</div>
|
| 666 |
-
<button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
|
| 667 |
<button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
|
| 668 |
<button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
|
| 669 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
<div class="control-group">
|
| 671 |
<label>Panel Tools (Select Panel):</label>
|
| 672 |
<button onclick="replacePanelImage()" class="action-button">🖼️ Replace Image</button>
|
|
@@ -758,6 +768,18 @@ class EnhancedComicGenerator:
|
|
| 758 |
currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
|
| 759 |
}
|
| 760 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
|
| 762 |
document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
|
| 763 |
document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
|
|
@@ -810,9 +832,8 @@ class EnhancedComicGenerator:
|
|
| 810 |
bubble.dataset.type = type;
|
| 811 |
|
| 812 |
if (type === 'speech') {
|
| 813 |
-
// <<< MODIFICATION: Set default tail position class >>>
|
| 814 |
bubble.classList.add('tail-bottom');
|
| 815 |
-
bubble.dataset.
|
| 816 |
}
|
| 817 |
if (type === 'thought') {
|
| 818 |
for (let i = 1; i <= 2; i++) {
|
|
@@ -826,6 +847,8 @@ class EnhancedComicGenerator:
|
|
| 826 |
function changeBubbleType(type) {
|
| 827 |
if (!currentlySelectedBubble) return;
|
| 828 |
applyBubbleType(currentlySelectedBubble, type);
|
|
|
|
|
|
|
| 829 |
}
|
| 830 |
|
| 831 |
function changeFont(font) {
|
|
@@ -833,20 +856,19 @@ class EnhancedComicGenerator:
|
|
| 833 |
currentlySelectedBubble.style.fontFamily = font;
|
| 834 |
}
|
| 835 |
|
| 836 |
-
// <<< MODIFICATION:
|
| 837 |
-
function
|
| 838 |
-
if (!currentlySelectedBubble
|
| 839 |
-
if (currentlySelectedBubble.dataset.type !== 'speech') {
|
| 840 |
-
alert("Tail rotation is only available for the 'Speech' bubble type.");
|
| 841 |
-
return;
|
| 842 |
-
}
|
| 843 |
|
| 844 |
-
const
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
currentlySelectedBubble.classList.
|
| 849 |
-
|
|
|
|
|
|
|
|
|
|
| 850 |
}
|
| 851 |
|
| 852 |
function selectPanel(panel) {
|
|
@@ -860,6 +882,7 @@ class EnhancedComicGenerator:
|
|
| 860 |
if (currentlySelectedBubble) currentlySelectedBubble.classList.remove('selected');
|
| 861 |
currentlySelectedBubble = bubble;
|
| 862 |
const bubbleControls = ['bubble-text-color', 'bubble-fill-color', 'bubble-type-select', 'font-select'];
|
|
|
|
| 863 |
|
| 864 |
if (currentlySelectedBubble) {
|
| 865 |
currentlySelectedBubble.classList.add('selected');
|
|
@@ -881,8 +904,22 @@ class EnhancedComicGenerator:
|
|
| 881 |
|
| 882 |
document.getElementById('zoom-slider').disabled = true;
|
| 883 |
bubbleControls.forEach(id => document.getElementById(id).disabled = false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 884 |
} else {
|
| 885 |
bubbleControls.forEach(id => document.getElementById(id).disabled = true);
|
|
|
|
| 886 |
if(currentlySelectedPanel) document.getElementById('zoom-slider').disabled = false;
|
| 887 |
}
|
| 888 |
}
|
|
|
|
| 543 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 544 |
.speech-bubble textarea { position: absolute; top: 0; left: 0; width: 100%; height: 100%; box-sizing: border-box; border: 1px solid #4CAF50; background: rgba(255,255,255,0.95); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
|
| 545 |
|
| 546 |
+
/* <<< MODIFICATION START: New CSS for Speech Bubble with Sliders >>> */
|
| 547 |
.speech-bubble.speech {
|
| 548 |
background: var(--bubble-fill-color, white);
|
| 549 |
color: var(--bubble-text-color, #333);
|
| 550 |
border-radius: .4em;
|
| 551 |
padding: 0;
|
| 552 |
border: none;
|
| 553 |
+
box-shadow: 0 1px 4px rgba(0,0,0,0.25);
|
| 554 |
}
|
| 555 |
|
| 556 |
.speech-bubble.speech::after {
|
|
|
|
| 558 |
position: absolute;
|
| 559 |
width: 0;
|
| 560 |
height: 0;
|
|
|
|
| 561 |
border-color: transparent;
|
| 562 |
border-style: solid;
|
| 563 |
+
/* Use CSS variables for dynamic control from sliders */
|
| 564 |
+
border-width: var(--tail-size, 20px);
|
| 565 |
}
|
| 566 |
|
|
|
|
| 567 |
.speech-bubble.speech.tail-bottom::after {
|
| 568 |
bottom: 0;
|
| 569 |
+
left: var(--tail-pos, 50%); /* Controlled by position slider */
|
|
|
|
| 570 |
border-top-color: var(--bubble-fill-color, white);
|
| 571 |
border-bottom: 0;
|
| 572 |
+
transform: translateX(-50%); /* Center the tip on the slider's value */
|
| 573 |
+
margin-bottom: calc(-1 * var(--tail-size, 20px));
|
| 574 |
}
|
| 575 |
.speech-bubble.speech.tail-top::after {
|
| 576 |
top: 0;
|
| 577 |
+
left: var(--tail-pos, 50%);
|
|
|
|
| 578 |
border-bottom-color: var(--bubble-fill-color, white);
|
| 579 |
border-top: 0;
|
| 580 |
+
transform: translateX(-50%);
|
| 581 |
+
margin-top: calc(-1 * var(--tail-size, 20px));
|
| 582 |
}
|
| 583 |
.speech-bubble.speech.tail-right::after {
|
| 584 |
+
top: var(--tail-pos, 50%);
|
| 585 |
right: 0;
|
|
|
|
| 586 |
border-left-color: var(--bubble-fill-color, white);
|
| 587 |
border-right: 0;
|
| 588 |
+
transform: translateY(-50%);
|
| 589 |
+
margin-right: calc(-1 * var(--tail-size, 20px));
|
| 590 |
}
|
| 591 |
.speech-bubble.speech.tail-left::after {
|
| 592 |
+
top: var(--tail-pos, 50%);
|
| 593 |
left: 0;
|
|
|
|
| 594 |
border-right-color: var(--bubble-fill-color, white);
|
| 595 |
border-left: 0;
|
| 596 |
+
transform: translateY(-50%);
|
| 597 |
+
margin-left: calc(-1 * var(--tail-size, 20px));
|
| 598 |
}
|
| 599 |
/* <<< MODIFICATION END >>> */
|
| 600 |
|
|
|
|
| 628 |
.color-picker-grid div { text-align: center; }
|
| 629 |
.color-picker-grid label { font-size: 11px; }
|
| 630 |
.color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
|
| 631 |
+
.slider-group { margin-top: 5px; }
|
| 632 |
+
.slider-group label { font-size: 11px; margin-right: 5px; }
|
| 633 |
</style>
|
| 634 |
</head>
|
| 635 |
<body>
|
|
|
|
| 661 |
<input type="color" id="bubble-fill-color" value="#FFFFFF" disabled>
|
| 662 |
</div>
|
| 663 |
</div>
|
|
|
|
| 664 |
<button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
|
| 665 |
<button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
|
| 666 |
</div>
|
| 667 |
+
<!-- <<< MODIFICATION: New Tail Controls Section >>> -->
|
| 668 |
+
<div class="control-group" id="tail-controls" style="display: none;">
|
| 669 |
+
<label>Tail Controls (Speech Only)</label>
|
| 670 |
+
<div class="slider-group">
|
| 671 |
+
<label for="tail-pos-slider">Position</label>
|
| 672 |
+
<input type="range" id="tail-pos-slider" min="10" max="90" value="50" step="1">
|
| 673 |
+
</div>
|
| 674 |
+
<div class="slider-group">
|
| 675 |
+
<label for="tail-size-slider">Size</label>
|
| 676 |
+
<input type="range" id="tail-size-slider" min="10" max="40" value="20" step="1">
|
| 677 |
+
</div>
|
| 678 |
+
<button onclick="flipBubbleTail()" class="secondary-button">🔄 Flip Edge</button>
|
| 679 |
+
</div>
|
| 680 |
<div class="control-group">
|
| 681 |
<label>Panel Tools (Select Panel):</label>
|
| 682 |
<button onclick="replacePanelImage()" class="action-button">🖼️ Replace Image</button>
|
|
|
|
| 768 |
currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
|
| 769 |
}
|
| 770 |
});
|
| 771 |
+
|
| 772 |
+
// <<< MODIFICATION: Event listeners for new tail sliders >>>
|
| 773 |
+
document.getElementById('tail-pos-slider').addEventListener('input', (e) => {
|
| 774 |
+
if (currentlySelectedBubble) {
|
| 775 |
+
currentlySelectedBubble.style.setProperty('--tail-pos', e.target.value + '%');
|
| 776 |
+
}
|
| 777 |
+
});
|
| 778 |
+
document.getElementById('tail-size-slider').addEventListener('input', (e) => {
|
| 779 |
+
if (currentlySelectedBubble) {
|
| 780 |
+
currentlySelectedBubble.style.setProperty('--tail-size', e.target.value + 'px');
|
| 781 |
+
}
|
| 782 |
+
});
|
| 783 |
|
| 784 |
document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
|
| 785 |
document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
|
|
|
|
| 832 |
bubble.dataset.type = type;
|
| 833 |
|
| 834 |
if (type === 'speech') {
|
|
|
|
| 835 |
bubble.classList.add('tail-bottom');
|
| 836 |
+
bubble.dataset.tailEdge = 'bottom';
|
| 837 |
}
|
| 838 |
if (type === 'thought') {
|
| 839 |
for (let i = 1; i <= 2; i++) {
|
|
|
|
| 847 |
function changeBubbleType(type) {
|
| 848 |
if (!currentlySelectedBubble) return;
|
| 849 |
applyBubbleType(currentlySelectedBubble, type);
|
| 850 |
+
// Reselect bubble to update UI controls visibility
|
| 851 |
+
selectBubble(currentlySelectedBubble);
|
| 852 |
}
|
| 853 |
|
| 854 |
function changeFont(font) {
|
|
|
|
| 856 |
currentlySelectedBubble.style.fontFamily = font;
|
| 857 |
}
|
| 858 |
|
| 859 |
+
// <<< MODIFICATION: Replaced rotate with flip function >>>
|
| 860 |
+
function flipBubbleTail() {
|
| 861 |
+
if (!currentlySelectedBubble || currentlySelectedBubble.dataset.type !== 'speech') return;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
|
| 863 |
+
const edges = ['bottom', 'right', 'top', 'left'];
|
| 864 |
+
const currentEdge = currentlySelectedBubble.dataset.tailEdge || 'bottom';
|
| 865 |
+
let currentEdgeIndex = edges.indexOf(currentEdge);
|
| 866 |
+
|
| 867 |
+
currentlySelectedBubble.classList.remove('tail-' + edges[currentEdgeIndex]);
|
| 868 |
+
let nextEdgeIndex = (currentEdgeIndex + 1) % edges.length;
|
| 869 |
+
|
| 870 |
+
currentlySelectedBubble.classList.add('tail-' + edges[nextEdgeIndex]);
|
| 871 |
+
currentlySelectedBubble.dataset.tailEdge = edges[nextEdgeIndex];
|
| 872 |
}
|
| 873 |
|
| 874 |
function selectPanel(panel) {
|
|
|
|
| 882 |
if (currentlySelectedBubble) currentlySelectedBubble.classList.remove('selected');
|
| 883 |
currentlySelectedBubble = bubble;
|
| 884 |
const bubbleControls = ['bubble-text-color', 'bubble-fill-color', 'bubble-type-select', 'font-select'];
|
| 885 |
+
const tailControls = document.getElementById('tail-controls');
|
| 886 |
|
| 887 |
if (currentlySelectedBubble) {
|
| 888 |
currentlySelectedBubble.classList.add('selected');
|
|
|
|
| 904 |
|
| 905 |
document.getElementById('zoom-slider').disabled = true;
|
| 906 |
bubbleControls.forEach(id => document.getElementById(id).disabled = false);
|
| 907 |
+
|
| 908 |
+
// <<< MODIFICATION: Show/hide tail controls and set slider values >>>
|
| 909 |
+
if (currentlySelectedBubble.dataset.type === 'speech') {
|
| 910 |
+
tailControls.style.display = 'block';
|
| 911 |
+
const currentSize = parseInt(styles.getPropertyValue('--tail-size')) || 20;
|
| 912 |
+
document.getElementById('tail-size-slider').value = currentSize;
|
| 913 |
+
|
| 914 |
+
const currentPos = parseInt(styles.getPropertyValue('--tail-pos')) || 50;
|
| 915 |
+
document.getElementById('tail-pos-slider').value = currentPos;
|
| 916 |
+
} else {
|
| 917 |
+
tailControls.style.display = 'none';
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
} else {
|
| 921 |
bubbleControls.forEach(id => document.getElementById(id).disabled = true);
|
| 922 |
+
tailControls.style.display = 'none';
|
| 923 |
if(currentlySelectedPanel) document.getElementById('zoom-slider').disabled = false;
|
| 924 |
}
|
| 925 |
}
|