jhh6576 commited on
Commit
4d819c5
·
verified ·
1 Parent(s): 0b4627b

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +116 -88
app_enhanced.py CHANGED
@@ -543,30 +543,101 @@ 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 Advanced 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: 25px;
 
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 {
557
  content: '';
558
  position: absolute;
559
- background: var(--bubble-fill-color, white);
560
-
561
- /* Use CSS variables for dynamic control */
562
- width: var(--tail-size, 25px);
563
- height: var(--tail-size, 25px);
564
- top: var(--tail-top);
565
- left: var(--tail-left);
566
-
567
- /* The magic happens here */
568
- transform: var(--tail-rotation, rotate(45deg));
569
- clip-path: polygon(var(--tail-shape, 50%) 0, 100% 100%, 0 100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  }
571
  /* <<< MODIFICATION END >>> */
572
 
@@ -605,8 +676,6 @@ class EnhancedComicGenerator:
605
  .color-picker-grid div { text-align: center; }
606
  .color-picker-grid label { font-size: 11px; }
607
  .color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
608
- .slider-group { margin-top: 5px; }
609
- .slider-group label { font-size: 11px; margin-right: 5px; }
610
  </style>
611
  </head>
612
  <body>
@@ -641,21 +710,9 @@ class EnhancedComicGenerator:
641
  <button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
642
  <button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
643
  </div>
644
- <!-- <<< MODIFICATION: New Tail Controls Section >>> -->
645
  <div class="control-group" id="tail-controls" style="display: none;">
646
  <label>Tail Controls</label>
647
- <div class="slider-group">
648
- <label for="tail-pos-slider">Position</label>
649
- <input type="range" id="tail-pos-slider" min="0" max="100" value="12" step="1">
650
- </div>
651
- <div class="slider-group">
652
- <label for="tail-size-slider">Size</label>
653
- <input type="range" id="tail-size-slider" min="15" max="50" value="25" step="1">
654
- </div>
655
- <div class="slider-group">
656
- <label for="tail-shape-slider">Shape</label>
657
- <input type="range" id="tail-shape-slider" min="0" max="100" value="50" step="1">
658
- </div>
659
  </div>
660
  <div class="control-group">
661
  <label>Panel Tools (Select Panel):</label>
@@ -748,21 +805,6 @@ class EnhancedComicGenerator:
748
  currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
749
  }
750
  });
751
-
752
- // <<< MODIFICATION: Event listeners for new tail sliders >>>
753
- document.getElementById('tail-pos-slider').addEventListener('input', (e) => {
754
- if (currentlySelectedBubble) updateTailPosition(e.target.value);
755
- });
756
- document.getElementById('tail-size-slider').addEventListener('input', (e) => {
757
- if (currentlySelectedBubble) {
758
- currentlySelectedBubble.style.setProperty('--tail-size', e.target.value + 'px');
759
- }
760
- });
761
- document.getElementById('tail-shape-slider').addEventListener('input', (e) => {
762
- if (currentlySelectedBubble) {
763
- currentlySelectedBubble.style.setProperty('--tail-shape', e.target.value + '%');
764
- }
765
- });
766
 
767
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
768
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
@@ -791,8 +833,6 @@ class EnhancedComicGenerator:
791
  bubbleDiv.style.left = data.left;
792
  bubbleDiv.style.top = data.top;
793
  applyBubbleType(bubbleDiv, 'speech');
794
- // Set initial tail position on creation
795
- updateTailPosition(12, bubbleDiv); // 12 is bottom-left quadrant
796
  return bubbleDiv;
797
  }
798
 
@@ -816,6 +856,10 @@ class EnhancedComicGenerator:
816
  bubble.classList.add(type);
817
  bubble.dataset.type = type;
818
 
 
 
 
 
819
  if (type === 'thought') {
820
  for (let i = 1; i <= 2; i++) {
821
  const dot = document.createElement('div');
@@ -835,39 +879,28 @@ class EnhancedComicGenerator:
835
  if (!currentlySelectedBubble) return;
836
  currentlySelectedBubble.style.fontFamily = font;
837
  }
838
-
839
- // <<< MODIFICATION: Function to handle circular tail positioning >>>
840
- function updateTailPosition(value, bubble = currentlySelectedBubble) {
841
- if (!bubble) return;
842
- bubble.dataset.tailCircularPos = value;
843
- const val = parseInt(value);
844
- let top, left, rotation;
845
-
846
- if (val >= 0 && val < 25) { // Bottom Edge
847
- const mappedVal = (val / 25) * 100;
848
- top = '100%';
849
- left = `${mappedVal}%`;
850
- rotation = '45deg';
851
- } else if (val >= 25 && val < 50) { // Right Edge
852
- const mappedVal = 100 - ((val - 25) / 25) * 100;
853
- top = `${mappedVal}%`;
854
- left = '100%';
855
- rotation = '135deg';
856
- } else if (val >= 50 && val < 75) { // Top Edge
857
- const mappedVal = 100 - ((val - 50) / 25) * 100;
858
- top = '0%';
859
- left = `${mappedVal}%`;
860
- rotation = '225deg';
861
- } else { // Left Edge
862
- const mappedVal = ((val - 75) / 25) * 100;
863
- top = `${mappedVal}%`;
864
- left = '0%';
865
- rotation = '315deg';
866
  }
867
-
868
- bubble.style.setProperty('--tail-top', top);
869
- bubble.style.setProperty('--tail-left', left);
870
- bubble.style.setProperty('--tail-rotation', `rotate(${rotation})`);
871
  }
872
 
873
  function selectPanel(panel) {
@@ -903,15 +936,10 @@ class EnhancedComicGenerator:
903
 
904
  document.getElementById('zoom-slider').disabled = true;
905
  bubbleControls.forEach(id => document.getElementById(id).disabled = false);
906
-
907
- if (currentlySelectedBubble.dataset.type === 'speech') {
 
908
  tailControls.style.display = 'block';
909
- const currentSize = parseInt(styles.getPropertyValue('--tail-size')) || 25;
910
- document.getElementById('tail-size-slider').value = currentSize;
911
- const currentShape = parseInt(styles.getPropertyValue('--tail-shape')) || 50;
912
- document.getElementById('tail-shape-slider').value = currentShape;
913
- const currentPos = currentlySelectedBubble.dataset.tailCircularPos || 12;
914
- document.getElementById('tail-pos-slider').value = currentPos;
915
  } else {
916
  tailControls.style.display = 'none';
917
  }
 
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 Robust Bordered Bubble CSS >>> */
547
  .speech-bubble.speech {
548
  background: var(--bubble-fill-color, white);
549
  color: var(--bubble-text-color, #333);
550
+ border: 2px solid var(--bubble-border-color, #333);
551
+ border-radius: 10px;
552
  padding: 0;
 
 
553
  }
554
 
555
+ .speech-bubble.speech:after, .speech-bubble.speech:before {
556
  content: '';
557
  position: absolute;
558
+ display: block;
559
+ width: 0;
560
+ }
561
+
562
+ /* 4-WAY TAIL ROTATION CLASSES */
563
+ /* BOTTOM */
564
+ .speech-bubble.speech.tail-bottom:after {
565
+ border-style: solid;
566
+ border-width: 15px 15px 0 0;
567
+ border-color: var(--bubble-fill-color, white) transparent transparent transparent;
568
+ bottom: -15px;
569
+ left: 50%;
570
+ transform: translateX(-50%);
571
+ z-index: 1;
572
+ }
573
+ .speech-bubble.speech.tail-bottom:before {
574
+ border-style: solid;
575
+ border-width: 17px 17px 0 0;
576
+ border-color: var(--bubble-border-color, #333) transparent transparent transparent;
577
+ bottom: -19px;
578
+ left: 50%;
579
+ transform: translateX(-50%);
580
+ z-index: 0;
581
+ }
582
+
583
+ /* TOP */
584
+ .speech-bubble.speech.tail-top:after {
585
+ border-style: solid;
586
+ border-width: 0 15px 15px 0;
587
+ border-color: transparent var(--bubble-fill-color, white) transparent transparent;
588
+ top: -15px;
589
+ left: 50%;
590
+ transform: translateX(-50%) rotate(180deg);
591
+ z-index: 1;
592
+ }
593
+ .speech-bubble.speech.tail-top:before {
594
+ border-style: solid;
595
+ border-width: 0 17px 17px 0;
596
+ border-color: transparent var(--bubble-border-color, #333) transparent transparent;
597
+ top: -19px;
598
+ left: 50%;
599
+ transform: translateX(-50%) rotate(180deg);
600
+ z-index: 0;
601
+ }
602
+
603
+ /* RIGHT */
604
+ .speech-bubble.speech.tail-right:after {
605
+ border-style: solid;
606
+ border-width: 15px 15px 0 0;
607
+ border-color: var(--bubble-fill-color, white) transparent transparent transparent;
608
+ top: 50%;
609
+ right: -15px;
610
+ transform: translateY(-50%) rotate(90deg);
611
+ z-index: 1;
612
+ }
613
+ .speech-bubble.speech.tail-right:before {
614
+ border-style: solid;
615
+ border-width: 17px 17px 0 0;
616
+ border-color: var(--bubble-border-color, #333) transparent transparent transparent;
617
+ top: 50%;
618
+ right: -19px;
619
+ transform: translateY(-50%) rotate(90deg);
620
+ z-index: 0;
621
+ }
622
+
623
+ /* LEFT */
624
+ .speech-bubble.speech.tail-left:after {
625
+ border-style: solid;
626
+ border-width: 15px 15px 0 0;
627
+ border-color: var(--bubble-fill-color, white) transparent transparent transparent;
628
+ top: 50%;
629
+ left: -15px;
630
+ transform: translateY(-50%) rotate(-90deg);
631
+ z-index: 1;
632
+ }
633
+ .speech-bubble.speech.tail-left:before {
634
+ border-style: solid;
635
+ border-width: 17px 17px 0 0;
636
+ border-color: var(--bubble-border-color, #333) transparent transparent transparent;
637
+ top: 50%;
638
+ left: -19px;
639
+ transform: translateY(-50%) rotate(-90deg);
640
+ z-index: 0;
641
  }
642
  /* <<< MODIFICATION END >>> */
643
 
 
676
  .color-picker-grid div { text-align: center; }
677
  .color-picker-grid label { font-size: 11px; }
678
  .color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
 
 
679
  </style>
680
  </head>
681
  <body>
 
710
  <button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
711
  <button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
712
  </div>
 
713
  <div class="control-group" id="tail-controls" style="display: none;">
714
  <label>Tail Controls</label>
715
+ <button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
 
 
 
 
 
 
 
 
 
 
 
716
  </div>
717
  <div class="control-group">
718
  <label>Panel Tools (Select Panel):</label>
 
805
  currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
806
  }
807
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
 
809
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
810
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
 
833
  bubbleDiv.style.left = data.left;
834
  bubbleDiv.style.top = data.top;
835
  applyBubbleType(bubbleDiv, 'speech');
 
 
836
  return bubbleDiv;
837
  }
838
 
 
856
  bubble.classList.add(type);
857
  bubble.dataset.type = type;
858
 
859
+ if (type === 'speech') {
860
+ bubble.classList.add('tail-bottom');
861
+ bubble.dataset.tailPos = '0';
862
+ }
863
  if (type === 'thought') {
864
  for (let i = 1; i <= 2; i++) {
865
  const dot = document.createElement('div');
 
879
  if (!currentlySelectedBubble) return;
880
  currentlySelectedBubble.style.fontFamily = font;
881
  }
882
+
883
+ function rotateBubbleTail() {
884
+ if (!currentlySelectedBubble) return;
885
+ const bubbleType = currentlySelectedBubble.dataset.type;
886
+
887
+ if (bubbleType === 'speech') {
888
+ const positions = ['tail-bottom', 'tail-right', 'tail-top', 'tail-left'];
889
+ let currentPos = parseInt(currentlySelectedBubble.dataset.tailPos || 0);
890
+ currentlySelectedBubble.classList.remove(positions[currentPos]);
891
+ let nextPos = (currentPos + 1) % positions.length;
892
+ currentlySelectedBubble.classList.add(positions[nextPos]);
893
+ currentlySelectedBubble.dataset.tailPos = nextPos;
894
+ } else if (bubbleType === 'thought') {
895
+ const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
896
+ const isFlippedV = currentlySelectedBubble.classList.contains('flipped-vertical');
897
+ if (!isFlippedH && !isFlippedV) { currentlySelectedBubble.classList.add('flipped'); }
898
+ else if (isFlippedH && !isFlippedV) { currentlySelectedBubble.classList.add('flipped-vertical'); }
899
+ else if (isFlippedH && isFlippedV) { currentlySelectedBubble.classList.remove('flipped'); }
900
+ else { currentlySelectedBubble.classList.remove('flipped-vertical'); }
901
+ } else {
902
+ alert("Tail rotation is only available for Speech and Thought bubbles.");
 
 
 
 
 
 
 
903
  }
 
 
 
 
904
  }
905
 
906
  function selectPanel(panel) {
 
936
 
937
  document.getElementById('zoom-slider').disabled = true;
938
  bubbleControls.forEach(id => document.getElementById(id).disabled = false);
939
+
940
+ const bubbleType = currentlySelectedBubble.dataset.type;
941
+ if (bubbleType === 'speech' || bubbleType === 'thought') {
942
  tailControls.style.display = 'block';
 
 
 
 
 
 
943
  } else {
944
  tailControls.style.display = 'none';
945
  }